2026-04-24 FE Backup
This commit is contained in:
BIN
Gateways/FE/projects/SDC_PHX_A7/ignition/global-props/data.bin
Normal file
BIN
Gateways/FE/projects/SDC_PHX_A7/ignition/global-props/data.bin
Normal file
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"data.bin"
|
||||
],
|
||||
"attributes": {}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
select id, settingname, settingvalue from user_settings
|
||||
where username= :username
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"scope": "DG",
|
||||
"version": 2,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"query.sql"
|
||||
],
|
||||
"attributes": {
|
||||
"useMaxReturnSize": false,
|
||||
"autoBatchEnabled": false,
|
||||
"fallbackValue": "",
|
||||
"maxReturnSize": 100,
|
||||
"cacheUnit": "SEC",
|
||||
"type": "Query",
|
||||
"enabled": true,
|
||||
"cacheAmount": 1,
|
||||
"cacheEnabled": false,
|
||||
"database": "Ignition",
|
||||
"fallbackEnabled": false,
|
||||
"lastModificationSignature": "50ee34967f6ef8d81281863fa8d094d3bcb8e04d35eae3e7aca25d6dbb24b0cf",
|
||||
"permissions": [
|
||||
{
|
||||
"zone": "",
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:00Z"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "username",
|
||||
"sqlType": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
SELECT
|
||||
id,
|
||||
body,
|
||||
last_updated,
|
||||
last_updated_by
|
||||
FROM
|
||||
tag_notes
|
||||
WHERE
|
||||
tag_path = :tagPath
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"scope": "DG",
|
||||
"version": 2,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"query.sql"
|
||||
],
|
||||
"attributes": {
|
||||
"useMaxReturnSize": false,
|
||||
"autoBatchEnabled": false,
|
||||
"fallbackValue": "",
|
||||
"maxReturnSize": 100,
|
||||
"cacheUnit": "SEC",
|
||||
"type": "Query",
|
||||
"enabled": true,
|
||||
"cacheAmount": 1,
|
||||
"cacheEnabled": false,
|
||||
"database": "",
|
||||
"fallbackEnabled": false,
|
||||
"lastModificationSignature": "0dd24dc57cc57d24576e0156d4aac7e16bfa206f7f6e6f118505920aca4759fc",
|
||||
"permissions": [
|
||||
{
|
||||
"zone": "",
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:53:59Z"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "tagPath",
|
||||
"sqlType": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO tag_notes (tag_path, body, last_updated, last_updated_by) VALUES (:tagPath, :body, CURRENT_TIMESTAMP, :user)
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"scope": "DG",
|
||||
"version": 2,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"query.sql"
|
||||
],
|
||||
"attributes": {
|
||||
"useMaxReturnSize": false,
|
||||
"autoBatchEnabled": false,
|
||||
"fallbackValue": "",
|
||||
"maxReturnSize": 100,
|
||||
"cacheUnit": "SEC",
|
||||
"type": "UpdateQuery",
|
||||
"enabled": true,
|
||||
"cacheAmount": 1,
|
||||
"cacheEnabled": false,
|
||||
"database": "",
|
||||
"fallbackEnabled": false,
|
||||
"lastModificationSignature": "48165516e5aa4fb1da2121b34dacec7aaeb5b5bc4e6a13fef9e2ed5efea023c1",
|
||||
"permissions": [
|
||||
{
|
||||
"zone": "",
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:53:59Z"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "tagPath",
|
||||
"sqlType": 7
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "body",
|
||||
"sqlType": 7
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "user",
|
||||
"sqlType": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
UPDATE tag_notes SET body = :body, last_updated = CURRENT_TIMESTAMP, last_updated_by = :user WHERE id = :id
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"scope": "DG",
|
||||
"version": 2,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"query.sql"
|
||||
],
|
||||
"attributes": {
|
||||
"useMaxReturnSize": false,
|
||||
"autoBatchEnabled": false,
|
||||
"fallbackValue": "",
|
||||
"maxReturnSize": 100,
|
||||
"cacheUnit": "SEC",
|
||||
"type": "UpdateQuery",
|
||||
"enabled": true,
|
||||
"cacheAmount": 1,
|
||||
"cacheEnabled": false,
|
||||
"database": "",
|
||||
"fallbackEnabled": false,
|
||||
"lastModificationSignature": "e6812051c27292002a4f15bc2d0c43b06e18b9a48bd5cecdc81aaca526778d57",
|
||||
"permissions": [
|
||||
{
|
||||
"zone": "",
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:53:59Z"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "body",
|
||||
"sqlType": 7
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "id",
|
||||
"sqlType": 2
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "user",
|
||||
"sqlType": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
SELECT
|
||||
id,
|
||||
body,
|
||||
last_updated,
|
||||
last_updated_by
|
||||
FROM
|
||||
wiki_notes
|
||||
WHERE
|
||||
model_no = :modelNo
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"scope": "DG",
|
||||
"version": 2,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"query.sql"
|
||||
],
|
||||
"attributes": {
|
||||
"useMaxReturnSize": false,
|
||||
"autoBatchEnabled": false,
|
||||
"fallbackValue": "",
|
||||
"maxReturnSize": 100,
|
||||
"cacheUnit": "SEC",
|
||||
"type": "Query",
|
||||
"enabled": true,
|
||||
"cacheAmount": 1,
|
||||
"cacheEnabled": false,
|
||||
"database": "",
|
||||
"fallbackEnabled": false,
|
||||
"lastModificationSignature": "5d06b7473439f0754df95e57c91a1c9de3b4de1de431c48f951f3ad17546228d",
|
||||
"permissions": [
|
||||
{
|
||||
"zone": "",
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:00Z"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "modelNo",
|
||||
"sqlType": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO wiki_notes (model_no, body, last_updated, last_updated_by) VALUES (:modelNo, :body, CURRENT_TIMESTAMP, :user)
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"scope": "DG",
|
||||
"version": 2,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"query.sql"
|
||||
],
|
||||
"attributes": {
|
||||
"useMaxReturnSize": false,
|
||||
"autoBatchEnabled": false,
|
||||
"fallbackValue": "",
|
||||
"maxReturnSize": 100,
|
||||
"cacheUnit": "SEC",
|
||||
"type": "UpdateQuery",
|
||||
"enabled": true,
|
||||
"cacheAmount": 1,
|
||||
"cacheEnabled": false,
|
||||
"database": "",
|
||||
"fallbackEnabled": false,
|
||||
"lastModificationSignature": "894dc84c99c78584b2b1c576d11e55cf01e3ce5d13c88de341339c25148f862f",
|
||||
"permissions": [
|
||||
{
|
||||
"zone": "",
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:53:59Z"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "body",
|
||||
"sqlType": 7
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "user",
|
||||
"sqlType": 7
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "modelNo",
|
||||
"sqlType": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
UPDATE wiki_notes SET body = :body, last_updated = CURRENT_TIMESTAMP, last_updated_by = :user WHERE id = :id
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"scope": "DG",
|
||||
"version": 2,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"query.sql"
|
||||
],
|
||||
"attributes": {
|
||||
"useMaxReturnSize": false,
|
||||
"autoBatchEnabled": false,
|
||||
"fallbackValue": "",
|
||||
"maxReturnSize": 100,
|
||||
"cacheUnit": "SEC",
|
||||
"type": "UpdateQuery",
|
||||
"enabled": true,
|
||||
"cacheAmount": 1,
|
||||
"cacheEnabled": false,
|
||||
"database": "",
|
||||
"fallbackEnabled": false,
|
||||
"lastModificationSignature": "e4ca2a7850ea86c99232674e1090e174bea7c23a9704d48de9d73fabc526009a",
|
||||
"permissions": [
|
||||
{
|
||||
"zone": "",
|
||||
"role": ""
|
||||
}
|
||||
],
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:00Z"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "body",
|
||||
"sqlType": 7
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "id",
|
||||
"sqlType": 2
|
||||
},
|
||||
{
|
||||
"type": "Parameter",
|
||||
"identifier": "user",
|
||||
"sqlType": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
def trace_power_flow():
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
ds_names = ["DS-1A1", "DS-1B1"]
|
||||
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/SESA_Out1', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/SESB_Out3'
|
||||
]
|
||||
for name in ds_names:
|
||||
# Assuming your UDT instances are in a folder named 'DS'
|
||||
base_path = "[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}".format(name)
|
||||
tag_paths_to_read.append("{}/Breaker Closed Way 01".format(base_path))
|
||||
tag_paths_to_read.append("{}/Breaker Closed Way 02".format(base_path))
|
||||
# 3. Read all tags at once
|
||||
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['SESA-FRD1_Live'] = tag_values[0].value
|
||||
values['SESB-FRD3_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ds_names:
|
||||
values[name] = {
|
||||
'Way01': tag_values[read_idx].value,
|
||||
'Way02': tag_values[read_idx + 1].value
|
||||
}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['SESA-FRD1_Live']:
|
||||
q.append( ("DS-1A1", "SESA-FRD1") )
|
||||
if values['SESB-FRD3_Live']:
|
||||
q.append( ("DS-1B1", "SESB-FRD3") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ds_name, source = q.pop(0)
|
||||
if current_ds_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ds_name)
|
||||
energized_state[current_ds_name] = source
|
||||
current_ut_index = ds_names.index(current_ds_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ds_names) - 1:
|
||||
neighbor_name = ds_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ds_name]['Way02'] and values[neighbor_name]['Way02']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ds_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ds_name]['Way02'] and values[neighbor_name]['Way02']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
print energized_state
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
for name in ds_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
|
||||
tag_paths_to_write.append("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}/Data/isEnergized".format(name))
|
||||
values_to_write.append(is_energized)
|
||||
|
||||
tag_paths_to_write.append("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}/Data/PowerSource".format(name))
|
||||
values_to_write.append(power_source)
|
||||
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "ed5d5728562ec7fb1eb5c70af41b9744783a2e8f8047790061812d12bc2e14e0",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
def trace_power_flow():
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
ds_names = ["DS-1A2", "DS-1B2"]
|
||||
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/SESA_Out2', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/SESB_Out2'
|
||||
]
|
||||
for name in ds_names:
|
||||
# Assuming your UDT instances are in a folder named 'DS'
|
||||
base_path = "[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}".format(name)
|
||||
tag_paths_to_read.append("{}/Breaker Closed Way 01".format(base_path))
|
||||
tag_paths_to_read.append("{}/Breaker Closed Way 02".format(base_path))
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['SESA-FRD2_Live'] = tag_values[0].value
|
||||
values['SESB-FRD2_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ds_names:
|
||||
values[name] = {
|
||||
'Way01': tag_values[read_idx].value,
|
||||
'Way02': tag_values[read_idx + 1].value
|
||||
}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['SESA-FRD2_Live']:
|
||||
q.append( ("DS-1A2", "SESA-FRD2") )
|
||||
if values['SESB-FRD2_Live']:
|
||||
q.append( ("DS-1B2", "SESB-FRD2") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ds_name, source = q.pop(0)
|
||||
|
||||
if current_ds_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ds_name)
|
||||
energized_state[current_ds_name] = source
|
||||
|
||||
current_ut_index = ds_names.index(current_ds_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ds_names) - 1:
|
||||
neighbor_name = ds_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ds_name]['Way02'] and values[neighbor_name]['Way02']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ds_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ds_name]['Way02'] and values[neighbor_name]['Way02']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
print energized_state
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
for name in ds_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
|
||||
tag_paths_to_write.append("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}/Data/isEnergized".format(name))
|
||||
values_to_write.append(is_energized)
|
||||
|
||||
tag_paths_to_write.append("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}/Data/PowerSource".format(name))
|
||||
values_to_write.append(power_source)
|
||||
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "b4bfe3f146a1804c988fe83bde0acd9e6aaad25aa5c28f3daec91141cb7fb926",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
def trace_power_flow():
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
ds_names = ["DS-1A3", "DS-1B3"]
|
||||
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/SESA_Out3', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/SESB_Out1'
|
||||
]
|
||||
for name in ds_names:
|
||||
# Assuming your UDT instances are in a folder named 'DS'
|
||||
base_path = "[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}".format(name)
|
||||
tag_paths_to_read.append("{}/Breaker Closed Way 01".format(base_path))
|
||||
tag_paths_to_read.append("{}/Breaker Closed Way 02".format(base_path))
|
||||
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['SESA-FRD3_Live'] = tag_values[0].value
|
||||
values['SESB-FRD1_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ds_names:
|
||||
values[name] = {
|
||||
'Way01': tag_values[read_idx].value,
|
||||
'Way02': tag_values[read_idx + 1].value
|
||||
}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['SESA-FRD3_Live']:
|
||||
q.append( ("DS-1A3", "SESA-FRD3") )
|
||||
if values['SESB-FRD1_Live']:
|
||||
q.append( ("DS-1B3", "SESB-FRD1") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ds_name, source = q.pop(0)
|
||||
|
||||
if current_ds_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ds_name)
|
||||
energized_state[current_ds_name] = source
|
||||
|
||||
current_ut_index = ds_names.index(current_ds_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ds_names) - 1:
|
||||
neighbor_name = ds_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ds_name]['Way02'] and values[neighbor_name]['Way02']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ds_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ds_name]['Way02'] and values[neighbor_name]['Way02']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
print energized_state
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
for name in ds_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
|
||||
tag_paths_to_write.append("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}/Data/isEnergized".format(name))
|
||||
values_to_write.append(is_energized)
|
||||
|
||||
tag_paths_to_write.append("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_{}/Data/PowerSource".format(name))
|
||||
values_to_write.append(power_source)
|
||||
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "9b732af351dad7161b4485bb89f5d950a03e5c78d97de96e100e5e8902de1349",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
def trace_power_flow():
|
||||
"""
|
||||
Traces power flow through a series of connected transformers (UTs).
|
||||
- Reads the state of all switches.
|
||||
- Determines which components are energized and from which source.
|
||||
- Writes the results back to memory tags.
|
||||
"""
|
||||
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
# [Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_UT-HS1
|
||||
ut_names = ["UT-P5-1", "UT-P5-2", "UT-P5-3", "UT-P5-4", "UT-P5-5"]
|
||||
providers = [ "[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_"]
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS3A_W3', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS3B_W3']
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
# Assuming your UDT instances are in a folder named 'UTs'
|
||||
base_path = providers[idx] + name
|
||||
tag_paths_to_read.append("{}/Source 1 Switch".format(base_path))
|
||||
tag_paths_to_read.append("{}/Source 2 Switch".format(base_path))
|
||||
idx+=1
|
||||
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['DS-3A_Live'] = tag_values[0].value
|
||||
values['DS-3B_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ut_names:
|
||||
values[name] = {
|
||||
'Switch_L_Status': tag_values[read_idx].value,
|
||||
'Switch_R_Status': tag_values[read_idx + 1].value}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['DS-3A_Live']:
|
||||
q.append( ("UT-P5-1", "DS-1A3") )
|
||||
if values['DS-3B_Live']:
|
||||
q.append( ("UT-P5-5", "DS-1B3") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ut_name, source = q.pop(0)
|
||||
|
||||
if current_ut_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ut_name)
|
||||
energized_state[current_ut_name] = source
|
||||
current_ut_index = ut_names.index(current_ut_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ut_names) - 1:
|
||||
neighbor_name = ut_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ut_name]['Switch_R_Status']['Value'] and values[neighbor_name]['Switch_L_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ut_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ut_name]['Switch_L_Status']['Value'] and values[neighbor_name]['Switch_R_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source))
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/isEnergized")
|
||||
values_to_write.append(is_energized)
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/PowerSource")
|
||||
values_to_write.append(power_source)
|
||||
idx += 1
|
||||
|
||||
line_tags_base_path = "[Ignition_Common_IO_Gtwy]Cables/UT/"
|
||||
#print energized_state
|
||||
# Loop through each connection between UTs
|
||||
for i in range(len(ut_names) - 1):
|
||||
ut1_name = ut_names[i]
|
||||
ut2_name = ut_names[i+1]
|
||||
line_energized = (ut1_name in energized_state and
|
||||
ut2_name in energized_state and
|
||||
#energized_state[ut1_name] == energized_state[ut2_name] and
|
||||
values[ut1_name]['Switch_R_Status']['Value'] and
|
||||
values[ut2_name]['Switch_L_Status']['Value'])
|
||||
#print ut1_name + " : " + str(ut1_name in energized_state) +" & "+str(values[ut1_name]['Switch_R_Status']['Value'])
|
||||
#print energized_state[ut1_name] + " = " + energized_state[ut2_name]
|
||||
#print ut2_name + " : " + str(ut2_name in energized_state) +" & "+str(values[ut1_name]['Switch_L_Status']['Value'])
|
||||
line_tag_name = "Line_{}_to_{}".format(ut1_name, ut2_name)
|
||||
# Construct the full tag path directly to the boolean tag
|
||||
full_tag_path = line_tags_base_path + line_tag_name
|
||||
# Add the path and the calculated value to our list for writing
|
||||
tag_paths_to_write.append(full_tag_path)
|
||||
values_to_write.append(line_energized)
|
||||
#print full_tag_path + " : " + str(line_energized)
|
||||
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "aa51734aec8b652af7e3451814626b923234d4b105435a6f40d0469e61d861e5",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
def trace_power_flow():
|
||||
"""
|
||||
Traces power flow through a series of connected transformers (UTs).
|
||||
- Reads the state of all switches.
|
||||
- Determines which components are energized and from which source.
|
||||
- Writes the results back to memory tags.
|
||||
"""
|
||||
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
# [Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_UT-HS1
|
||||
|
||||
#Temp suppres due to IO4 not available
|
||||
ut_names = ["UT-P4-1", "UT-P4-2", "UT-P4-3", "UT-P4-4", "UT-P4-5", "UT-HS2"]
|
||||
providers = [ "[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_"]
|
||||
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS3A_W4', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS3B_W4']
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
# Assuming your UDT instances are in a folder named 'UTs'
|
||||
base_path = providers[idx] + name
|
||||
tag_paths_to_read.append("{}/Source 1 Switch".format(base_path))
|
||||
tag_paths_to_read.append("{}/Source 2 Switch".format(base_path))
|
||||
idx+=1
|
||||
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['DS-3A_Live'] = tag_values[0].value
|
||||
values['DS-3B_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ut_names:
|
||||
values[name] = {
|
||||
'Switch_L_Status': tag_values[read_idx].value,
|
||||
'Switch_R_Status': tag_values[read_idx + 1].value
|
||||
}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['DS-3A_Live']:
|
||||
q.append( ("UT-P4-1", "DS-1A3") )
|
||||
if values['DS-3B_Live']:
|
||||
q.append( ("UT-HS2", "DS-1B3") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ut_name, source = q.pop(0)
|
||||
|
||||
if current_ut_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ut_name)
|
||||
energized_state[current_ut_name] = source
|
||||
current_ut_index = ut_names.index(current_ut_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ut_names) - 1:
|
||||
neighbor_name = ut_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ut_name]['Switch_R_Status']['Value'] and values[neighbor_name]['Switch_L_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ut_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ut_name]['Switch_L_Status']['Value'] and values[neighbor_name]['Switch_R_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source))
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/isEnergized")
|
||||
values_to_write.append(is_energized)
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/PowerSource")
|
||||
values_to_write.append(power_source)
|
||||
idx += 1
|
||||
|
||||
line_tags_base_path = "[Ignition_Common_IO_Gtwy]Cables/UT/"
|
||||
#print energized_state
|
||||
# Loop through each connection between UTs
|
||||
for i in range(len(ut_names) - 1):
|
||||
ut1_name = ut_names[i]
|
||||
ut2_name = ut_names[i+1]
|
||||
line_energized = (ut1_name in energized_state and
|
||||
ut2_name in energized_state and
|
||||
#energized_state[ut1_name] == energized_state[ut2_name] and
|
||||
values[ut1_name]['Switch_R_Status']['Value'] and
|
||||
values[ut2_name]['Switch_L_Status']['Value'])
|
||||
#print ut1_name + " : " + str(ut1_name in energized_state) +" & "+str(values[ut1_name]['Switch_R_Status']['Value'])
|
||||
#print energized_state[ut1_name] + " = " + energized_state[ut2_name]
|
||||
#print ut2_name + " : " + str(ut2_name in energized_state) +" & "+str(values[ut1_name]['Switch_L_Status']['Value'])
|
||||
line_tag_name = "Line_{}_to_{}".format(ut1_name, ut2_name)
|
||||
# Construct the full tag path directly to the boolean tag
|
||||
full_tag_path = line_tags_base_path + line_tag_name
|
||||
# Add the path and the calculated value to our list for writing
|
||||
tag_paths_to_write.append(full_tag_path)
|
||||
values_to_write.append(line_energized)
|
||||
#print full_tag_path + " : " + str(line_energized)
|
||||
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "dc6966341a2163b4682d0a38d71ef3ff16e8bc9cb78c77275dab48147240f9cd",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
def trace_power_flow():
|
||||
"""
|
||||
Traces power flow through a series of connected transformers (UTs).
|
||||
- Reads the state of all switches.
|
||||
- Determines which components are energized and from which source.
|
||||
- Writes the results back to memory tags.
|
||||
"""
|
||||
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
# [Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_UT-HS1
|
||||
ut_names = ["UT-P3-1", "UT-P3-2", "UT-P3-3", "UT-P3-4", "UT-P3-5"]
|
||||
providers = [ "[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_"]
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS2A_W3', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS2B_W3']
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
# Assuming your UDT instances are in a folder named 'UTs'
|
||||
base_path = providers[idx] + name
|
||||
tag_paths_to_read.append("{}/Source 1 Switch".format(base_path))
|
||||
tag_paths_to_read.append("{}/Source 2 Switch".format(base_path))
|
||||
idx+=1
|
||||
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['DS-2A_Live'] = tag_values[0].value
|
||||
values['DS-2B_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ut_names:
|
||||
values[name] = {
|
||||
'Switch_L_Status': tag_values[read_idx].value,
|
||||
'Switch_R_Status': tag_values[read_idx + 1].value}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['DS-2A_Live']:
|
||||
q.append( ("UT-P3-1", "DS-1A2") )
|
||||
if values['DS-2B_Live']:
|
||||
q.append( ("UT-P3-5", "DS-1B2") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ut_name, source = q.pop(0)
|
||||
|
||||
if current_ut_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ut_name)
|
||||
energized_state[current_ut_name] = source
|
||||
current_ut_index = ut_names.index(current_ut_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ut_names) - 1:
|
||||
neighbor_name = ut_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ut_name]['Switch_R_Status']['Value'] and values[neighbor_name]['Switch_L_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ut_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ut_name]['Switch_L_Status']['Value'] and values[neighbor_name]['Switch_R_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source))
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/isEnergized")
|
||||
values_to_write.append(is_energized)
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/PowerSource")
|
||||
values_to_write.append(power_source)
|
||||
idx += 1
|
||||
|
||||
line_tags_base_path = "[Ignition_Common_IO_Gtwy]Cables/UT/"
|
||||
#print energized_state
|
||||
#print values
|
||||
# Loop through each connection between UTs
|
||||
for i in range(len(ut_names) - 1):
|
||||
ut1_name = ut_names[i]
|
||||
ut2_name = ut_names[i+1]
|
||||
line_energized = (ut1_name in energized_state and
|
||||
ut2_name in energized_state and
|
||||
#energized_state[ut1_name] == energized_state[ut2_name] and
|
||||
values[ut1_name]['Switch_R_Status']['Value'] and
|
||||
values[ut2_name]['Switch_L_Status']['Value'])
|
||||
#print ut1_name + " : " + str(ut1_name in energized_state) +" & "+str(values[ut1_name]['Switch_R_Status']['Value'])
|
||||
#print energized_state[ut1_name] + " = " + energized_state[ut2_name]
|
||||
#print ut2_name + " : " + str(ut2_name in energized_state) +" & "+str(values[ut2_name]['Switch_L_Status']['Value'])
|
||||
line_tag_name = "Line_{}_to_{}".format(ut1_name, ut2_name)
|
||||
# Construct the full tag path directly to the boolean tag
|
||||
full_tag_path = line_tags_base_path + line_tag_name
|
||||
# Add the path and the calculated value to our list for writing
|
||||
tag_paths_to_write.append(full_tag_path)
|
||||
values_to_write.append(line_energized)
|
||||
#print full_tag_path + " : " + str(line_energized)
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "d2264103d03ca219bd76f5f3740835941d09b6b9fab43277f008d537968f6950",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:15Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
def trace_power_flow():
|
||||
"""
|
||||
Traces power flow through a series of connected transformers (UTs).
|
||||
- Reads the state of all switches.
|
||||
- Determines which components are energized and from which source.
|
||||
- Writes the results back to memory tags.
|
||||
"""
|
||||
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
# [Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_UT-HS1
|
||||
ut_names = ["UT-P2-1", "UT-P2-2", "UT-P2-3", "UT-P2-4", "UT-P2-5"]
|
||||
providers = ["[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_"]
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS2A_W4', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS2B_W4']
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
# Assuming your UDT instances are in a folder named 'UTs'
|
||||
base_path = providers[idx] + name
|
||||
tag_paths_to_read.append("{}/Source 1 Switch".format(base_path))
|
||||
tag_paths_to_read.append("{}/Source 2 Switch".format(base_path))
|
||||
idx+=1
|
||||
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['DS-2A_Live'] = tag_values[0].value
|
||||
values['DS-2B_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ut_names:
|
||||
values[name] = {
|
||||
'Switch_L_Status': tag_values[read_idx].value,
|
||||
'Switch_R_Status': tag_values[read_idx + 1].value}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['DS-2A_Live']:
|
||||
q.append( ("UT-P2-1", "DS-1A2") )
|
||||
if values['DS-2B_Live']:
|
||||
q.append( ("UT-P2-5", "DS-1B2") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ut_name, source = q.pop(0)
|
||||
|
||||
if current_ut_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ut_name)
|
||||
energized_state[current_ut_name] = source
|
||||
current_ut_index = ut_names.index(current_ut_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ut_names) - 1:
|
||||
neighbor_name = ut_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ut_name]['Switch_R_Status']['Value'] and values[neighbor_name]['Switch_L_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ut_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ut_name]['Switch_L_Status']['Value'] and values[neighbor_name]['Switch_R_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source))
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/isEnergized")
|
||||
values_to_write.append(is_energized)
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/PowerSource")
|
||||
values_to_write.append(power_source)
|
||||
idx += 1
|
||||
|
||||
line_tags_base_path = "[Ignition_Common_IO_Gtwy]Cables/UT/"
|
||||
#print energized_state
|
||||
# Loop through each connection between UTs
|
||||
for i in range(len(ut_names) - 1):
|
||||
ut1_name = ut_names[i]
|
||||
ut2_name = ut_names[i+1]
|
||||
line_energized = (ut1_name in energized_state and
|
||||
ut2_name in energized_state and
|
||||
#energized_state[ut1_name] == energized_state[ut2_name] and
|
||||
values[ut1_name]['Switch_R_Status']['Value'] and
|
||||
values[ut2_name]['Switch_L_Status']['Value'])
|
||||
#print ut1_name + " : " + str(ut1_name in energized_state) +" & "+str(values[ut1_name]['Switch_R_Status']['Value'])
|
||||
#print energized_state[ut1_name] + " = " + energized_state[ut2_name]
|
||||
#print ut2_name + " : " + str(ut2_name in energized_state) +" & "+str(values[ut1_name]['Switch_L_Status']['Value'])
|
||||
line_tag_name = "Line_{}_to_{}".format(ut1_name, ut2_name)
|
||||
# Construct the full tag path directly to the boolean tag
|
||||
full_tag_path = line_tags_base_path + line_tag_name
|
||||
# Add the path and the calculated value to our list for writing
|
||||
tag_paths_to_write.append(full_tag_path)
|
||||
values_to_write.append(line_energized)
|
||||
#print full_tag_path + " : " + str(line_energized)
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "1c695fdc2f6f2a0a26d56429e3e2112a4e23820c44f16282ae38b36feae0a370",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
def trace_power_flow():
|
||||
"""
|
||||
Traces power flow through a series of connected transformers (UTs).
|
||||
- Reads the state of all switches.
|
||||
- Determines which components are energized and from which source.
|
||||
- Writes the results back to memory tags.
|
||||
"""
|
||||
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
# [Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_UT-HS1
|
||||
ut_names = ["UT-M1", "UT-M2", "UT-M3", "UT-M4", "UT-M5", "UT-M6"]
|
||||
providers = ["[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",]
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS1A_W3', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS1B_W3']
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
# Assuming your UDT instances are in a folder named 'UTs'
|
||||
base_path = providers[idx] + name
|
||||
tag_paths_to_read.append("{}/Source 1 Switch".format(base_path))
|
||||
tag_paths_to_read.append("{}/Source 2 Switch".format(base_path))
|
||||
idx+=1
|
||||
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['DS-1A_Live'] = tag_values[0].value
|
||||
values['DS-1B_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ut_names:
|
||||
values[name] = {
|
||||
'Switch_L_Status': tag_values[read_idx].value,
|
||||
'Switch_R_Status': tag_values[read_idx + 1].value}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['DS-1A_Live']:
|
||||
q.append( ("UT-M1", "DS-1A1") )
|
||||
if values['DS-1B_Live']:
|
||||
q.append( ("UT-M6", "DS-1B1") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ut_name, source = q.pop(0)
|
||||
|
||||
if current_ut_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ut_name)
|
||||
energized_state[current_ut_name] = source
|
||||
current_ut_index = ut_names.index(current_ut_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ut_names) - 1:
|
||||
neighbor_name = ut_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ut_name]['Switch_R_Status']['Value'] and values[neighbor_name]['Switch_L_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ut_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ut_name]['Switch_L_Status']['Value'] and values[neighbor_name]['Switch_R_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source))
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/isEnergized")
|
||||
values_to_write.append(is_energized)
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/PowerSource")
|
||||
values_to_write.append(power_source)
|
||||
idx += 1
|
||||
|
||||
line_tags_base_path = "[Ignition_Common_IO_Gtwy]Cables/UT/"
|
||||
#print energized_state
|
||||
# Loop through each connection between UTs
|
||||
for i in range(len(ut_names) - 1):
|
||||
ut1_name = ut_names[i]
|
||||
ut2_name = ut_names[i+1]
|
||||
line_energized = (ut1_name in energized_state and
|
||||
ut2_name in energized_state and
|
||||
#energized_state[ut1_name] == energized_state[ut2_name] and
|
||||
values[ut1_name]['Switch_R_Status']['Value'] and
|
||||
values[ut2_name]['Switch_L_Status']['Value'])
|
||||
#print ut1_name + " : " + str(ut1_name in energized_state) +" & "+str(values[ut1_name]['Switch_R_Status']['Value'])
|
||||
#print energized_state[ut1_name] + " = " + energized_state[ut2_name]
|
||||
#print ut2_name + " : " + str(ut2_name in energized_state) +" & "+str(values[ut1_name]['Switch_L_Status']['Value'])
|
||||
line_tag_name = "Line_{}_to_{}".format(ut1_name, ut2_name)
|
||||
# Construct the full tag path directly to the boolean tag
|
||||
full_tag_path = line_tags_base_path + line_tag_name
|
||||
# Add the path and the calculated value to our list for writing
|
||||
tag_paths_to_write.append(full_tag_path)
|
||||
values_to_write.append(line_energized)
|
||||
#print full_tag_path + " : " + str(line_energized)
|
||||
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "daffcc145abf608df8dadc3944a09827b336f75fa21ce05ef6d07080eb22b38c",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
def trace_power_flow():
|
||||
"""
|
||||
Traces power flow through a series of connected transformers (UTs).
|
||||
- Reads the state of all switches.
|
||||
- Determines which components are energized and from which source.
|
||||
- Writes the results back to memory tags.
|
||||
"""
|
||||
|
||||
# 1. Define the network structure in order from left to right
|
||||
# This makes it easy to find neighbors.
|
||||
# [Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_UT-HS1
|
||||
ut_names = ["UT-HS1", "UT-P1-1", "UT-P1-2", "UT-P1-3", "UT-P1-4", "UT-P1-5", "UT-M7", "UT-M8"]
|
||||
providers = ["[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO1_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO1_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO1_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO1_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_SATB1_IO1_default]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_"]
|
||||
# 2. Build the list of all tags we need to read
|
||||
# Using readBlocking for a single, efficient tag read
|
||||
tag_paths_to_read = [
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS1A_W4', # Assumes a tag for the source status
|
||||
'[Ignition_Common_IO_Gtwy]Cables/SES/DS1B_W4']
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
# Assuming your UDT instances are in a folder named 'UTs'
|
||||
base_path = providers[idx] + name
|
||||
tag_paths_to_read.append("{}/Source 1 Switch".format(base_path))
|
||||
tag_paths_to_read.append("{}/Source 2 Switch".format(base_path))
|
||||
idx+=1
|
||||
|
||||
# 3. Read all tags at once
|
||||
try:
|
||||
tag_values = system.tag.readBlocking(tag_paths_to_read)
|
||||
except Exception as e:
|
||||
# Log error if tags can't be read
|
||||
system.util.getLogger("PowerTrace").error("Error reading tags: {}".format(e))
|
||||
return
|
||||
|
||||
# Create a dictionary for easy access to tag values
|
||||
# e.g., values['UT-HS1']['Switch_L_Status']
|
||||
values = {}
|
||||
values['DS-1A_Live'] = tag_values[0].value
|
||||
values['DS-1B_Live'] = tag_values[1].value
|
||||
|
||||
read_idx = 2
|
||||
for name in ut_names:
|
||||
values[name] = {
|
||||
'Switch_L_Status': tag_values[read_idx].value,
|
||||
'Switch_R_Status': tag_values[read_idx + 1].value}
|
||||
read_idx += 2
|
||||
|
||||
# 4. The Tracing Algorithm
|
||||
# This dictionary will store the final state: e.g., energized_state['UT P1.2'] = 'DS-1A1'
|
||||
energized_state = {}
|
||||
|
||||
# A queue for our BFS traversal, storing (ut_name, source)
|
||||
# Using a list as a queue: append to add, pop(0) to remove from front
|
||||
q = []
|
||||
|
||||
# Initialize the queue with active sources
|
||||
if values['DS-1A_Live']:
|
||||
q.append( ("UT-HS1", "DS-1A1") )
|
||||
if values['DS-1B_Live']:
|
||||
q.append( ("UT-M8", "DS-1B1") )
|
||||
|
||||
# Process the queue until it's empty
|
||||
visited = set() # Keep track of UTs we've already processed to prevent infinite loops
|
||||
while q:
|
||||
current_ut_name, source = q.pop(0)
|
||||
|
||||
if current_ut_name in visited:
|
||||
continue
|
||||
|
||||
visited.add(current_ut_name)
|
||||
energized_state[current_ut_name] = source
|
||||
current_ut_index = ut_names.index(current_ut_name)
|
||||
|
||||
# Check for propagation to the RIGHT (-->)
|
||||
if current_ut_index < len(ut_names) - 1:
|
||||
neighbor_name = ut_names[current_ut_index + 1]
|
||||
# Condition: Current UT's right switch is closed AND Neighbor's left switch is closed
|
||||
if values[current_ut_name]['Switch_R_Status']['Value'] and values[neighbor_name]['Switch_L_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source) )
|
||||
|
||||
# Check for propagation to the LEFT (<--)
|
||||
if current_ut_index > 0:
|
||||
neighbor_name = ut_names[current_ut_index - 1]
|
||||
# Condition: Current UT's left switch is closed AND Neighbor's right switch is closed
|
||||
if values[current_ut_name]['Switch_L_Status']['Value'] and values[neighbor_name]['Switch_R_Status']['Value']:
|
||||
if neighbor_name not in visited:
|
||||
q.append( (neighbor_name, source))
|
||||
|
||||
# 5. Prepare and write the results back to Ignition tags
|
||||
tag_paths_to_write = []
|
||||
values_to_write = []
|
||||
|
||||
# Set UT states
|
||||
idx = 0
|
||||
for name in ut_names:
|
||||
is_energized = name in energized_state
|
||||
power_source = energized_state.get(name, 'None')
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/isEnergized")
|
||||
values_to_write.append(is_energized)
|
||||
tag_paths_to_write.append(providers[idx] + name + "/Data/PowerSource")
|
||||
values_to_write.append(power_source)
|
||||
idx += 1
|
||||
|
||||
line_tags_base_path = "[Ignition_Common_IO_Gtwy]Cables/UT/"
|
||||
#print energized_state
|
||||
# Loop through each connection between UTs
|
||||
i = 0
|
||||
for i in range(len(ut_names) - 1):
|
||||
ut1_name = ut_names[i]
|
||||
ut2_name = ut_names[i+1]
|
||||
line_energized = (ut1_name in energized_state and
|
||||
ut2_name in energized_state and
|
||||
#energized_state[ut1_name] == energized_state[ut2_name] and
|
||||
values[ut1_name]['Switch_R_Status']['Value'] and
|
||||
values[ut2_name]['Switch_L_Status']['Value'])
|
||||
#print ut1_name + " : " + str(ut1_name in energized_state) +" & "+str(values[ut1_name]['Switch_R_Status']['Value'])
|
||||
#print energized_state[ut1_name] + " = " + energized_state[ut2_name]
|
||||
#print ut2_name + " : " + str(ut2_name in energized_state) +" & "+str(values[ut1_name]['Switch_L_Status']['Value'])
|
||||
line_tag_name = "Line_{}_to_{}".format(ut1_name, ut2_name)
|
||||
# Construct the full tag path directly to the boolean tag
|
||||
full_tag_path = line_tags_base_path + line_tag_name
|
||||
# Add the path and the calculated value to our list for writing
|
||||
tag_paths_to_write.append(full_tag_path)
|
||||
values_to_write.append(line_energized)
|
||||
#print full_tag_path + " : " + str(line_energized)
|
||||
|
||||
# Write all values in a single, efficient call
|
||||
if tag_paths_to_write:
|
||||
system.tag.writeBlocking(tag_paths_to_write, values_to_write)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "ac86cff99153ba73c3a107377850f75a1ef82bd56c00a42386f8fb4c94cbca28",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
def update_alarms_in_udts(root_path):
|
||||
"""This script will update all the alarms under a base path so that the
|
||||
alarm is enabled based on the depth from the UDT and the other Meta tag bindings are updated accordingly."""
|
||||
|
||||
def get_relative_meta_path(depth):
|
||||
if depth <= 1:
|
||||
return "[.]Meta/"
|
||||
return "[.]" + "../" * (depth - 1) + "Meta/"
|
||||
|
||||
def update_alarm_config(base_path, path, meta_path):
|
||||
"""This function will take a tag path as an input and update the alarms with depth-based meta references."""
|
||||
configs = system.tag.getConfiguration(path, False)
|
||||
|
||||
for config in configs:
|
||||
if "alarms" in config:
|
||||
for alarm in config["alarms"]:
|
||||
print alarm
|
||||
alarm["Building"] = {"bindType": "Tag", "value": meta_path + "Building"}
|
||||
alarm["EqName"] = {"bindType": "Tag", "value": meta_path + "EqName"}
|
||||
alarm["AreaServed"] = {"bindType": "Tag", "value": meta_path + "Area Served"}
|
||||
alarm["Room"] = {"bindType": "Tag", "value": meta_path + "Room"}
|
||||
if "enabled" not in alarm or alarm['enabled']==True:
|
||||
alarm["enabled"] = {"bindType": "Tag", "value": meta_path + "Alarms Enabled"}
|
||||
else:
|
||||
# Check to see if the alarm if false. If false, skip edit.
|
||||
if alarm["enabled"]:
|
||||
enable_condition = str(alarm["enabled"]["value"])
|
||||
alarm["enabled"] = {"bindType": "Expression", "value": "{%sAlarms Enabled} && {%s}" % (meta_path, enable_condition)}
|
||||
# take the tag path and use in the expression
|
||||
# u'enabled': {u'bindType': u'Expression', u'value': u'{[.]../Meta/Alarms Enabled} && {[.]Communcation Loss}'}
|
||||
system.tag.configure(base_path, config, "o")
|
||||
print "Updated tag: %s" % config["path"]
|
||||
else:
|
||||
print "\nERROR: Tag has no alarms: %s\n" % config["path"]
|
||||
|
||||
def recurse_tags(base_path, udt_depth):
|
||||
"""Recursively browse tags and update alarms with respect to UDT depth."""
|
||||
tags = system.tag.browse(base_path)
|
||||
|
||||
for tag in tags:
|
||||
tag_type = str(tag["tagType"])
|
||||
has_children = tag["hasChildren"]
|
||||
full_path = tag["fullPath"]
|
||||
|
||||
if tag_type == "UdtType":
|
||||
# Entering a new UDT, so depth resets to 1
|
||||
recurse_tags(full_path, 1)
|
||||
elif tag_type == "AtomicTag" and "alarm" in tag["attributes"]:
|
||||
meta_path = get_relative_meta_path(udt_depth)
|
||||
update_alarm_config(base_path, full_path, meta_path)
|
||||
elif has_children:
|
||||
# Descending deeper from current UDT level
|
||||
recurse_tags(full_path, udt_depth + 1)
|
||||
|
||||
recurse_tags(root_path, 0)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "6de9a9776b2ada70fb30cf99ee571f029230b025a127b455c84eeef7f03b32a2",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
def update_alarms_in_udts(root_path): #, enable_tag_path, custom_props):
|
||||
"""This script will update all the alarms under a base path so that the
|
||||
alarm is enabled based on the "Meta/Alarms Enabled" tag, and the Building, EqName,
|
||||
Area Served, Location, and Alarms Enabled properties have been set to their Meta tag equivalent."""
|
||||
|
||||
def update_alarm_config(base_path, path):
|
||||
"""This function will take a tag path as an input and update the alarms."""
|
||||
|
||||
configs = system.tag.getConfiguration(path, False)
|
||||
|
||||
for config in configs:
|
||||
if "alarms" in config:
|
||||
for alarm in config["alarms"]:
|
||||
alarm["ackPipeline"] = {"bindType": "UDTParameter", "value": "{AckPipeline}"}
|
||||
alarm["activePipeline"] = {"bindType": "UDTParameter", "value": "{ActivePipeline}"}
|
||||
alarm["clearPipeline"] = {"bindType": "UDTParameter", "value": "{ClearPipeline}"}
|
||||
system.tag.configure(base_path, config, "o")
|
||||
print "The following tag was update: %s" % config["path"]
|
||||
else:
|
||||
print "\nERROR: The following tag did not have an alarm: %s\n" % config["path"]
|
||||
|
||||
|
||||
def recurse_tags(base_path):
|
||||
"""Locate all the alarms underneath a base_path and call update_alarm_config when found."""
|
||||
|
||||
tags = system.tag.browse(base_path)
|
||||
for tag in tags:
|
||||
if str(tag["tagType"]) == "UdtType":
|
||||
isUdt = True
|
||||
else:
|
||||
isUdt = False
|
||||
|
||||
if str(tag["tagType"]) == "AtomicTag" and "alarm" in tag["attributes"]:
|
||||
update_alarm_config(base_path, tag["fullPath"])
|
||||
elif tag["hasChildren"]:
|
||||
recurse_tags(tag["fullPath"])
|
||||
|
||||
recurse_tags(root_path)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "2544878c5b9f0a3162a38dfaffb1290391660c0a4011d0b51563572f0028118b",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
def exportAlarms():
|
||||
state = ['ActiveUnacked']
|
||||
|
||||
alarms_list = system.alarm.queryStatus(state = state)
|
||||
# Define column headers for your dataset
|
||||
headers = ["Source", "Display Path", "Current State", "Priority"]
|
||||
|
||||
# Create a list of lists to hold the alarm data
|
||||
data = []
|
||||
for alarm in alarms_list:
|
||||
row = [
|
||||
alarm.getSource(),
|
||||
alarm.getDisplayPath(),
|
||||
alarm.getState(),
|
||||
alarm.getPriority(),
|
||||
]
|
||||
data.append(row)
|
||||
|
||||
alarms_dataset = system.dataset.toDataSet(headers, data)
|
||||
|
||||
filepath = system.dataset.exportCSV("data.csv", 1, alarms_dataset)
|
||||
|
||||
if fielPath != None:
|
||||
system.net.openURL("file:///"+filePath.replace('\\','/'))
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "ef7ad09a3b817d7a4149189d7fc7ce7b66695cce299bfd74f44af370611083b9",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import pprint, time
|
||||
Logger = system.util.getLogger("alarm.summary")
|
||||
provider = "[default]"
|
||||
|
||||
def initAlarmSummaries():
|
||||
""" This function will reset all the alarm summary udt's in the system
|
||||
|
||||
"""
|
||||
allActiveAlarmsTag = []
|
||||
newValues = []
|
||||
for t in system.tag.browse(provider, {"tagType":"UdtInstance", "typeId": "AlarmSummary", "recursive":True}):
|
||||
allActiveAlarmsTag.append(t["fullPath"].toString() +"/ActiveAlarms")
|
||||
newValues.append({})
|
||||
|
||||
system.tag.writeAsync(allActiveAlarmsTag, newValues)
|
||||
|
||||
def updateAlarmCount(srcPath, priority, state):
|
||||
pass
|
||||
|
||||
|
||||
def calcMaxPriority(levelPath):
|
||||
""" Pass in the path level to determine the max priority and return the value
|
||||
levelPath: string of the object level path without the /AlarmSummary/ActiveAlarms
|
||||
"""
|
||||
aggAlarmStatus = system.tag.readBlocking([levelPath+"/AlarmSummary/ActiveAlarms"])[0].value.toDict()
|
||||
|
||||
return determinePriority(aggAlarmStatus)
|
||||
|
||||
def determinePriority(tagValue):
|
||||
""" Use this method to figure out the highest priority level of a tag. Useful with transforms from front-end
|
||||
Args:
|
||||
tagValue: dict/document of the expected structure of {srcPath:priority}
|
||||
Returns:
|
||||
maxState: int -1 for none, 0-Diag, 1-Low, 2- Med, 3- High, 4-Critical
|
||||
"""
|
||||
maxState = -1
|
||||
for k, v in tagValue.iteritems():
|
||||
maxState = v if v > maxState else maxState
|
||||
|
||||
return maxState
|
||||
|
||||
|
||||
|
||||
def syncAlarms():
|
||||
"""
|
||||
Timer based function to update all of the aggregate alarm tags.
|
||||
|
||||
"""
|
||||
sublog = Logger.createSubLogger("syncAlarms")
|
||||
start = time.time()
|
||||
levelAlarmSummary = {} #{"alarmPath":[{srcAlarm:priority}]
|
||||
for alm in system.alarm.queryStatus(priority=[0,1,2,3,4],state=['ActiveUnacked','ActiveAcked']):
|
||||
srcPath = alm.getSource().toString()
|
||||
mainTagPath = srcPath.split("/tag:")[1].split(":/alm")[0]
|
||||
priorityVal = alm.getPriority().intValue
|
||||
|
||||
pathLevelsList = mainTagPath.split("/")
|
||||
|
||||
# Because alarms can exist outside of the /Alarms folder in the UDT, we will account for this
|
||||
almOffset = -1
|
||||
if "Alarms" not in pathLevelsList:
|
||||
almOffset= 0
|
||||
|
||||
for i in range(1, len(pathLevelsList) + almOffset):
|
||||
path = provider+"/".join(pathLevelsList[:i])+"/AlarmSummary/ActiveAlarms"
|
||||
if path not in levelAlarmSummary.keys():
|
||||
levelAlarmSummary[path]= {}
|
||||
|
||||
levelAlarmSummary[path][srcPath] = max([levelAlarmSummary[path].get(srcPath,0), priorityVal ])
|
||||
|
||||
# pprint.pprint(levelAlarmSummary)
|
||||
writeTags, writeVals = [], []
|
||||
for k, v in levelAlarmSummary.iteritems():
|
||||
writeTags.append(str(k))
|
||||
writeVals.append(v)
|
||||
sublog.debug("%s - %s"%(writeTags, writeVals))
|
||||
if len(writeTags) == len(writeVals) and len(writeTags) > 0:
|
||||
system.tag.writeAsync(writeTags, writeVals)
|
||||
|
||||
|
||||
sublog.trace("finished in %s sec"%(time.time() - start))
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 7,
|
||||
"lastModificationSignature": "36649fc8abff8060b71390e2f49afe477ea8e0096329614f217eda8372941ad9",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:15Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
from com.inductiveautomation.ignition.common.alarming.config.CommonAlarmProperties import EventTime
|
||||
priorityBGColors = {"Diagnostic":"#FFFFFF", "Low":"#7266B7", "Medium":"#F4B834", "High":"#DE7C33", "Critical":"#E22028"}
|
||||
priorityTextColors = {"Diagnostic":"#222222", "Low":"#FAFAFB", "Medium":"#FAFAFB", "High":"#FAFAFB", "Critical":"#FAFAFB"}
|
||||
|
||||
def getTop3Alarms():
|
||||
# import time
|
||||
# start= time.time()
|
||||
res= []
|
||||
|
||||
activeAlarms = system.alarm.queryStatus([], ["ActiveUnacked"], ["*"], [], [])
|
||||
sortedAlarms = system.dataset.toPyDataSet(system.dataset.sort(activeAlarms.getDataset(),3,False))
|
||||
|
||||
formatAlarms = [{'eventid':str(alm.EventId), "label":alm.get('label'), "dispPath":alm.getDisplayPathOrSource(), "eventtime":alm.getActiveData().get(EventTime), "priority":str(alm.getPriority())} for alm in activeAlarms]
|
||||
top3EventIDs = [alm["EventId"] for alm in sortedAlarms[:3]]
|
||||
for eid in top3EventIDs:
|
||||
for alm in formatAlarms:
|
||||
if alm["eventid"] == eid:
|
||||
res.append({"value":{"dispPath":alm["dispPath"],"label":alm["label"]}, "style":{"backgroundColor":priorityBGColors[alm["priority"]],
|
||||
"color":priorityTextColors[alm["priority"]]}})
|
||||
break
|
||||
# system.perspective.print("alarm update duration %s"%(time.time()- start))
|
||||
return res
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "e341a1798bc82f48656303e0c6d816f4f5adf74cac28b4132878419205df89de",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# Find ALL the devices with a String, set 1 to Enable, 0 to Disable them
|
||||
def UpdateAllDevices(target):
|
||||
deviceDataset = system.device.listDevices()
|
||||
for i in range(0, deviceDataset.getRowCount()):
|
||||
deviceName = deviceDataset.getValueAt(i, "Name")
|
||||
if target == 0:
|
||||
system.device.setDeviceEnabled(deviceName, target)
|
||||
print "Device: Disabled %s"%(deviceName)
|
||||
if target == 1:
|
||||
system.device.setDeviceEnabled(deviceName, target)
|
||||
print "Device: Enabled %s"%(deviceName)
|
||||
|
||||
#Update the devices with a specific string in the name. 1 to enable - 0 to disable
|
||||
def UpdateDeviceType(taget, deviceType):
|
||||
deviceDataset = system.device.listDevices()
|
||||
for i in range(0, deviceDataset.getRowCount()):
|
||||
deviceName = deviceDataset.getValueAt(i, "Name")
|
||||
if deviceName.find(deviceType)>-1:
|
||||
if target == 0:
|
||||
system.device.setDeviceEnabled(deviceName, target)
|
||||
print "Device: Disabled %s"%(deviceName)
|
||||
if target == 1:
|
||||
system.device.setDeviceEnabled(deviceName, target)
|
||||
print "Device: Enabled %s"%(deviceName)
|
||||
|
||||
def UpdatePanelParams(path, namespace, opc, tagname):
|
||||
subpaths = [{'name':'/AC Primary Power Fault', 'ext':'.JA_01'},
|
||||
{'name':'/AC Secondary Power Fault', 'ext':'.JA_02'},
|
||||
{'name':'/DC Power Supply 1 Fault', 'ext':'.JA_03'},
|
||||
{'name':'/DC Power Supply 2 Fault', 'ext':'.JA_04'},
|
||||
{'name':'/High Temperature', 'ext':'.TAH'},
|
||||
{'name':'/Panel Intrusion', 'ext':'.XA'}]
|
||||
print "===== Updating Panel Parameters ====="
|
||||
for subtag in subpaths:
|
||||
tagpath = str(path) + str(subtag['name'])
|
||||
tag = system.tag.getConfiguration(tagpath)
|
||||
name = str(tagname) + str(subtag['ext'])
|
||||
tag[0]['parameters']['Namespace'] = namespace
|
||||
tag[0]['parameters']['OPC'] = opc
|
||||
tag[0]['parameters']['Tag'] = name
|
||||
system.tag.configure(path, tag, "o")
|
||||
print "updating: " + str(subtag)
|
||||
print "===== Panel Parameters Updated ====="
|
||||
|
||||
def UpdateUTParams(path, namespace, opc, tagname):
|
||||
subpaths = [{'name':'/Liquid Level Alarm', 'ext':'.LA'},
|
||||
{'name':'/Liquid Temperature Alarm High', 'ext':'.TAH'},
|
||||
{'name':'/Liquid Temperature Alarm High High', 'ext':'.TAHH'},
|
||||
{'name':'/Pressure Relief Device Alarm', 'ext':'.PRD_ALM'},
|
||||
{'name':'/Pressure Switch', 'ext':'.PSW'},
|
||||
{'name':'/Rapid Rise Relay Tripped', 'ext':'.RRR_TRIP'},
|
||||
{'name':'/Rapid Rise Relay Warning', 'ext':'.RRR_ALM'},
|
||||
{'name':'/Source 1 Switch', 'ext':'.ZSO01'},
|
||||
{'name':'/Source 2 Switch', 'ext':'.ZSO02'},
|
||||
{'name':'/Vacuum Fault Interrupter1 Tripped', 'ext':'.VFI01'},
|
||||
{'name':'/Vacuum Fault Interrupter2 Tripped', 'ext':'.VFI02'},
|
||||
{'name':'/Vacuum Switch', 'ext':'.VSW'}]
|
||||
|
||||
for subtag in subpaths:
|
||||
tagpath = str(path) + str(subtag['name'])
|
||||
tag = system.tag.getConfiguration(tagpath)
|
||||
name = str(tagname) + str(subtag['ext'])
|
||||
tag[0]['parameters']['Namespace'] = namespace
|
||||
tag[0]['parameters']['OPC'] = opc
|
||||
tag[0]['parameters']['Tag'] = name
|
||||
system.tag.configure(path, tag, "o")
|
||||
|
||||
#def setHistory(UDTNAme):
|
||||
#parentPath = "[default]_types_/Objects/TEST"
|
||||
#configs = system.tag.getConfiguration(parentPath, True)
|
||||
|
||||
#for folder in configs[0]['tags']:
|
||||
# UDTName = [folder['name']][0]
|
||||
# print UDTName
|
||||
# UDTs = system.tag.getConfiguration(parentPath +'/'+ UDTName, True)
|
||||
# for tag in UDTs[0]['tags']:
|
||||
# if str(tag['tagType']) == 'AtomicTag':
|
||||
# tag['historyEnabled'] = True
|
||||
# print tag
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "a4438c3c50df8495f839d01fc8b494cd7b042d5c1757d91511c0b5f54bd73a5e",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
def toggleCommandPaths (resetTagPath, enableTagPath):
|
||||
"""
|
||||
This function receives two paths, resetTagPath set to False, then the enabletagPath is set to True
|
||||
Params:
|
||||
resetTagPath: string
|
||||
enableTagPath: string
|
||||
Returns:
|
||||
none
|
||||
"""
|
||||
|
||||
def writeSeparate():
|
||||
def writeEnable(asyncReturn):
|
||||
system.tag.writeBlocking([enableTagPath],[1])
|
||||
system.tag.writeAsync([resetTagPath],[0], writeEnable)
|
||||
|
||||
|
||||
def writeAll():
|
||||
system.tag.writeBlocking([resetTagPath, enableTagPath], [0,1])
|
||||
|
||||
writeAll()
|
||||
# writeSeparate()
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "3884853f03c1677b8c792ca6ec847b3304067a5c1b1622618f314a38755d5f6e",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
def set_DOAS():
|
||||
tagpaths = ["[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS01/Config/High Supply Air Temperature Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS01/Control/Schedule Force Occupied",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS01/Control/Supply Air Dewpoint Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS01/Control/Supply Air Temperature Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS02/Config/High Supply Air Temperature Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS02/Control/Schedule Force Occupied",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS02/Control/Supply Air Dewpoint Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS02/Control/Supply Air Temperature Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS03/Config/High Supply Air Temperature Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS03/Control/Schedule Force Occupied",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS03/Control/Supply Air Dewpoint Setpoint",
|
||||
"[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_DOAS03/Control/Supply Air Temperature Setpoint"
|
||||
]
|
||||
data = system.tag.readBlocking(tagpaths)
|
||||
#print data
|
||||
idx = 0
|
||||
w_paths = []
|
||||
w_values = []
|
||||
for i in range(0,3):
|
||||
if data[idx].value == 0:
|
||||
w_paths.append(tagpaths[idx])
|
||||
w_values.append(68)
|
||||
if data[idx+1].value == 0:
|
||||
w_paths.append(tagpaths[idx+1])
|
||||
w_values.append(1)
|
||||
if data[idx+2].value == 0:
|
||||
w_paths.append(tagpaths[idx+2])
|
||||
w_values.append(55)
|
||||
if data[idx+3].value == 0:
|
||||
w_paths.append(tagpaths[idx+3])
|
||||
w_values.append(65)
|
||||
idx += 4
|
||||
if w_paths:
|
||||
print "Writing values"
|
||||
try:
|
||||
system.tag.writeBlocking(w_paths, w_values)
|
||||
except:
|
||||
print "Error"
|
||||
else:
|
||||
print "Zeros not found"
|
||||
|
||||
def set_FCU():
|
||||
fcus = ["[Ignition_Common_IO_Gtwy]ER1185/SATB1_HS2_EL1185_FCU01",
|
||||
"[Ignition_Common_IO_Gtwy]MDF1187/SATB1_HS2_MDF1187_FCU02",
|
||||
"[Ignition_Common_IO_Gtwy]MDF1187/SATB1_HS2_MDF1187_FCU03",
|
||||
"[Ignition_Common_IO_Gtwy]ER1160/SATB1_HS2_EL1160_FCU04",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1174/SATB1_HS2_IDF1174_FCU05",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1154/SATB1_HS2_IDF1154_FCU06",
|
||||
"[Ignition_Common_IO_Gtwy]MDF1149/SATB1_HS2_MDF1149_FCU07",
|
||||
"[Ignition_Common_IO_Gtwy]MDF1149/SATB1_HS2_MDF1149_FCU08",
|
||||
"[Ignition_Common_IO_Gtwy]FS1285/SATB1_HS2_FS1285_FCU09",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1286/SATB1_HS2_IDF1286_FCU10",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1286/SATB1_HS2_IDF1286_FCU11",
|
||||
"[Ignition_Common_IO_Gtwy]ER1287/SATB1_HS2_EL1287_FCU12",
|
||||
"[Ignition_Common_IO_Gtwy]ER1284/SATB1_HS2_EL1284_FCU13",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1274/SATB1_HS2_IDF1274_FCU14",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1264/SATB1_HS2_IDF1264_FCU15",
|
||||
"[Ignition_Common_IO_Gtwy]ER1128/SATB1_HS2_EL1128_FCU16",
|
||||
"[Ignition_Common_IO_Gtwy]MR1125/SATB1_HS2_ME1125_FCU17",
|
||||
"[Ignition_Common_IO_Gtwy]ER1229/SATB1_HS2_EL1229_FCU18",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1218/SATB1_HS2_IDF1218_FCU19",
|
||||
"[Ignition_Common_IO_Gtwy]IDF1122/SATB1_HS2_IDF1122_FCU20",
|
||||
"[Ignition_Common_IO_Gtwy]FS1232/SATB1_HS2_FS1232_FCU21",
|
||||
"[Ignition_Common_IO_Gtwy]MR1132/SATB1_HS2_ME1132_FCU22",
|
||||
"[Ignition_Common_IO_Gtwy]RM1138/SATB1_HS2_RM1138_FCU23",
|
||||
"[Ignition_Common_IO_Gtwy]RM1138/SATB1_HS2_RM1138_FCU24",
|
||||
"[Ignition_Common_IO_Gtwy]RM1138/SATB1_HS2_RM1138_FCU25",
|
||||
"[Ignition_Common_IO_Gtwy]RM1138/SATB1_HS2_RM1138_FCU26",
|
||||
"[Ignition_Common_IO_Gtwy]RM1219/SATB1_HS2_RM1219_FCU27",
|
||||
"[Ignition_Common_IO_Gtwy]RM1193/SATB1_HS2_RM1193_FCU28",
|
||||
"[Ignition_Common_IO_Gtwy]RM1234/SATB1_HS2_RM1234_FCU29"]
|
||||
|
||||
tagpaths = []
|
||||
for fcu in fcus:
|
||||
tagpaths.append(fcu + "/Config/Space Temperature Cooling Setpoint")
|
||||
tagpaths.append(fcu + "/Config/Space Temperature Cooling Setpoint DB")
|
||||
|
||||
data = system.tag.readBlocking(tagpaths)
|
||||
#for d in data:
|
||||
# print d
|
||||
idx = 0
|
||||
w_paths = []
|
||||
w_values = []
|
||||
for i in range(0,29):
|
||||
if data[idx].value == 0:
|
||||
w_paths.append(tagpaths[idx])
|
||||
w_values.append(74)
|
||||
if data[idx+1].value == 0:
|
||||
w_paths.append(tagpaths[idx+1])
|
||||
w_values.append(1)
|
||||
idx += 2
|
||||
if w_paths:
|
||||
print "writing values"
|
||||
try:
|
||||
system.tag.writeBlocking(w_paths, w_values)
|
||||
except:
|
||||
print "Error"
|
||||
else:
|
||||
print "Zeros not found"
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "0d7cc7d2974a944e1da1eab2282d019d2930240755b62dc0bbcb4586418b1bbf",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
def groupJournalByPathAndState(events):
|
||||
"""
|
||||
Creates a summary list of dicts with displayPath, eventState, and occurrence count.
|
||||
|
||||
Returns list sorted by displayPath (alphabetical), then by eventState.
|
||||
"""
|
||||
if not events:
|
||||
return []
|
||||
|
||||
groups = {} # outer: source → {eventState: count}
|
||||
|
||||
for event in events:
|
||||
|
||||
es = event.get('eventState')
|
||||
source = event.get('source')
|
||||
|
||||
# Get or create inner dict
|
||||
inner = groups.setdefault(source, {})
|
||||
# Increment count (inner.setdefault would also work, but +1 is clearer)
|
||||
inner[es] = inner.get(es, 0) + 1
|
||||
|
||||
# Build flat list of result dictionaries
|
||||
result = []
|
||||
|
||||
for source in sorted(groups.keys()): # sort displayPath alphabetically
|
||||
for es in sorted(groups[source].keys()): # sort eventState within each path
|
||||
|
||||
result.append([source, es, groups[source][es]])
|
||||
|
||||
return system.dataset.toDataSet(["source", "eventState", "count"], result)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "c1ae1a36c86e01ef45a97b06345fcb5066bee4242a034579df6bc3d535115394",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
def getColumnOrder(columns_config):
|
||||
visible_columns = [k for k, v in sorted(columns_config.items(), key=lambda x: x[1]["order"])
|
||||
if v["enabled"]]
|
||||
return visible_columns
|
||||
|
||||
def fromObjToDS(data, column_order=None):
|
||||
"""
|
||||
Converts a list of dictionaries into an Ignition PyDataSet.
|
||||
|
||||
Parameters:
|
||||
data: list[dict] - list of dictionaries where keys are column names
|
||||
column_order: list[str] (optional) - explicit column order.
|
||||
If None, uses the keys from the fi row.
|
||||
|
||||
Returns:
|
||||
PyDataSet - Ignition dataset object
|
||||
"""
|
||||
if len(data)== 0:
|
||||
# Return empty dataset with no columns if input is invalid/empty
|
||||
|
||||
return system.dataset.toDataSet([], [])
|
||||
|
||||
# Determine column names
|
||||
if column_order is not None:
|
||||
headers = column_order
|
||||
else:
|
||||
# Use keys from first dictionary (Python 3.7+ preserves insertion order)
|
||||
headers = list(data[0].keys())
|
||||
|
||||
# Build rows
|
||||
rows = []
|
||||
for row_dict in data:
|
||||
# Create row tuple using the determined column order
|
||||
row = []
|
||||
for col in headers:
|
||||
# Use .get() so missing keys become None (standard dataset behavior)
|
||||
value = row_dict.get(col, None)
|
||||
row.append(value)
|
||||
rows.append(row)
|
||||
|
||||
# Create the actual dataset
|
||||
return system.dataset.toDataSet(headers, rows)
|
||||
|
||||
def true_keys(data):
|
||||
"""
|
||||
Returns a list of dictionary keys whose values are strictly True.
|
||||
|
||||
Parameters:
|
||||
data (dict): A dictionary with boolean values.
|
||||
|
||||
Returns:
|
||||
list[str]: A list of keys where the value is True, or an empty list
|
||||
if none match.
|
||||
|
||||
Example:
|
||||
priorities = {
|
||||
"diagnostic": True,
|
||||
"low": False,
|
||||
"medium": False,
|
||||
"high": False,
|
||||
"critical": False
|
||||
}
|
||||
|
||||
result = true_keys(priorities)
|
||||
# result => ['diagnostic']
|
||||
"""
|
||||
|
||||
return [key for key, value in data.items() if value is True]
|
||||
|
||||
def parse_event_filter_to_query_states(events):
|
||||
"""
|
||||
Build a list of event state labels from the given filter.
|
||||
|
||||
Input:
|
||||
events: A container supporting membership tests (e.g., set/list/dict keys),
|
||||
where possible keys are:
|
||||
- 'active' -> include active states
|
||||
- 'cleared' -> include cleared states
|
||||
- 'acked' -> refine to only acked states (removes *Unacked)
|
||||
|
||||
Behavior:
|
||||
- If 'active' in events: add ['activeUnacked']
|
||||
- If 'cleared' in events: add ['clearedUnacked']
|
||||
- If 'acked' in events: remove any label containing 'Un' (i.e., keep only *Acked)
|
||||
|
||||
Returns:
|
||||
list[str]: A list of labels, e.g.,
|
||||
['activeUnacked', 'activeAcked', 'clearedUnacked', 'clearedAcked']
|
||||
reduced by the 'acked' refiner if present.
|
||||
|
||||
Examples:
|
||||
parse_event_filter_to_query_states({'active'})
|
||||
-> ['ActiveUnacked', 'ActiveAcked']
|
||||
|
||||
parse_event_filter_to_query_states({'cleared', 'acked'})
|
||||
-> ['ClearAcked']
|
||||
|
||||
parse_event_filter_to_query_states({'active', 'cleared', 'acked'})
|
||||
-> ['ActiveAcked', 'ClearAcked']
|
||||
|
||||
parse_event_filter_to_query_states({'active', 'cleared', 'acked'})
|
||||
-> ['ClearAcked', 'ActiveAcked']
|
||||
"""
|
||||
|
||||
ret = []
|
||||
|
||||
if 'active' in events:
|
||||
ret += ['ActiveUnacked']
|
||||
|
||||
if 'cleared' in events:
|
||||
ret += ['ClearUnacked']
|
||||
|
||||
if 'acked' in events:
|
||||
if len(ret) == 0:
|
||||
ret += ['ActiveAcked', 'ClearAcked']
|
||||
else:
|
||||
if 'active' in events:
|
||||
ret += ['ActiveAcked']
|
||||
if 'cleared' in events:
|
||||
ret += ['ClearAcked']
|
||||
|
||||
return ret
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "a12fe09e0f6676442fe57c13fff6eaca1002a86a33595aea7a91ba69dedf3699",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
from datetime import datetime
|
||||
def getBaseTagPaths(locations=[]):
|
||||
"""
|
||||
Get a list of tagpaths from SES1A/SES1B or the selected datahalls.
|
||||
"""
|
||||
baseTagPaths = []
|
||||
if len(locations) > 0:
|
||||
for location in locations:
|
||||
baseTagPaths+= reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getPQMBase(), location)
|
||||
else:
|
||||
baseTagPaths = reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getUtilitySES(), "Yard")
|
||||
return baseTagPaths
|
||||
|
||||
|
||||
def getEnergyPaths(locations):
|
||||
return ["%s/Real Energy"%(path) for path in getBaseTagPaths(locations)]
|
||||
|
||||
def getRawData(startDate, endDate, locations):
|
||||
"""
|
||||
Get per minute data for all of the energy paths.
|
||||
"""
|
||||
energyTagPathsSES = getEnergyPaths([])
|
||||
energyTagPathsAreas = getEnergyPaths(locations)
|
||||
sesHist = reports.MBR.common.util.getHistory(energyTagPathsSES, startDate, endDate, customArgs={})
|
||||
areaHist = reports.MBR.common.util.getHistory(energyTagPathsAreas, startDate, endDate, customArgs={})
|
||||
|
||||
return sesHist, areaHist
|
||||
|
||||
def calculateDelta(dataset, sumColName):
|
||||
"""
|
||||
Pass in the dataset and use the sumColName to indicate which column the delta will be performed
|
||||
Args:
|
||||
dataset: PyDataset of rawhist and sum column
|
||||
sumColname: name of the column the sum resides in
|
||||
Returns:
|
||||
List of calculated deltas
|
||||
"""
|
||||
deltaList = []
|
||||
for i,row in enumerate(dataset):
|
||||
if i == 0:
|
||||
deltaList.append(0)
|
||||
else:
|
||||
deltaList.append(row[sumColName]-dataset[i-1][sumColName])
|
||||
return deltaList
|
||||
|
||||
def processRawData(startDate, endDate, locations, intervalMins=15):
|
||||
"""
|
||||
Retrieve the raw history minute data, then create the new columns
|
||||
"""
|
||||
# PyDS
|
||||
sesDS, areaDS = getRawData(startDate, endDate, locations)
|
||||
sesDS = reports.MBR.common.util.generateSumCol(reports.MBR.common.util.averageIntervalData(sesDS, intervalMins), sumColName="SESTotalEnergy")
|
||||
areaDS = reports.MBR.common.util.generateSumCol(reports.MBR.common.util.averageIntervalData(areaDS, intervalMins), sumColName="ExclAreaTotalEnergy")
|
||||
|
||||
sesDeltaList = calculateDelta(sesDS, "SESTotalEnergy")
|
||||
areaDeltaList = calculateDelta(areaDS, "ExclAreaTotalEnergy")
|
||||
finalDS = sesDS
|
||||
|
||||
if len(sesDeltaList) == len(areaDeltaList):
|
||||
deltaDiffList = [a-b for a,b in zip(sesDeltaList, areaDeltaList)]
|
||||
|
||||
# merge the 2 SES and Area Datasets
|
||||
# try:
|
||||
finalDS = reports.MBR.common.util.mergeHistorianData(sesDS, areaDS)
|
||||
finalDS = system.dataset.addColumn(finalDS, sesDeltaList, "SES Consumed", float)
|
||||
finalDS = system.dataset.addColumn(finalDS, areaDeltaList, "Excluded Area Consumed", float)
|
||||
finalDS = system.dataset.addColumn(finalDS, deltaDiffList, "Tenant Consumed", float)
|
||||
|
||||
# except:
|
||||
# pass
|
||||
else:
|
||||
finalDS = system.dataset.addColumn(finalDS, col= sesDeltaList, colName="SES Consumed", colType=float)
|
||||
|
||||
return finalDS
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "e4c85a51878f4d13dc0f3c44dbbc071acc44b8f267c073c79c2d151f10ee0bed",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
locBasePath= {"DH1":"[Ignition_Common_IO_Gtwy]DH1/SATB1_DH1_PNL24_",
|
||||
"DH2":"[Ignition_Common_IO_Gtwy]DH2/SATB1_DH2_PNL22_",
|
||||
"DH3":"[Ignition_Common_IO_Gtwy]DH3/SATB1_DH3_PNL18_",
|
||||
"DH4":"[Ignition_Common_IO_Gtwy]DH4/SATB1_DH4_PNL20_",
|
||||
"DH5":"[Ignition_Common_IO_Gtwy]DH5/SATB1_DH5_PNL16_",
|
||||
}
|
||||
|
||||
def loopConstructPaths(basePath, pfx):
|
||||
tagList = []
|
||||
for aisle in range(1,22):
|
||||
for pos in range(1,4):
|
||||
# print aisle, pos
|
||||
fullPath = basePath+"C%s_%s%s/Val"%(aisle, pfx, str(pos).zfill(2))
|
||||
tagList.append(fullPath)
|
||||
return tagList
|
||||
|
||||
"""
|
||||
Generate the tagpaths needed for a particular cold aisle tags for a location
|
||||
Args:
|
||||
location: str (DH1-DH5)
|
||||
sensorType: str (temp or humidity)
|
||||
Returns:
|
||||
list of strings of paths
|
||||
"""
|
||||
def generateTags(location, sensorType):
|
||||
finalTags = []
|
||||
sensorPfx = "TT" if sensorType == "temp" else "HT"
|
||||
if location is None:
|
||||
for k,v in locBasePath.iteritems():
|
||||
finalTags.extend(loopConstructPaths(v,sensorPfx))
|
||||
else:
|
||||
finalTags.extend(loopConstructPaths(locBasePath[location],sensorPfx))
|
||||
|
||||
|
||||
return finalTags
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "adcde55290e5b73f9f0c8b195ace8b825f44035273fbe2387d5dc4aeee1325fe",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
def GenerateCompleteReport(startDate, endDate, location):
|
||||
|
||||
# reports = ["MBR/0-Cover", "MBR/1-Total Power", "MBR/2-CriticalPower", "MBR/3-Cooling", "MBR/4-Generators"]
|
||||
# reportBytes = []
|
||||
# for report in reports:
|
||||
# repParams= {"StartDate":startDate, "EndDate":endDate, "location":location}
|
||||
# print report
|
||||
# try:
|
||||
# bytes = system.report.executeReport(path=report, parameters=repParams, fileType="pdf")
|
||||
# except:
|
||||
# bytes= system.report.executeReport(path=report,project=system.util.getProjectName(), parameters=repParams, fileType="pdf")
|
||||
#
|
||||
# reportBytes.append(bytes)
|
||||
#
|
||||
# return system.artek.mergeMultiPDF(reportBytes)
|
||||
|
||||
return null
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "2bbf2545a9a0e8eaf51578864f4ab7448923274c3f5ea02383609b09c723dd8c",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
emptyDict={
|
||||
"Common":["a"],
|
||||
"DH1":["a"],
|
||||
"DH2":[],
|
||||
"DH3":[],
|
||||
"DH4":[],
|
||||
"DH5":[],
|
||||
"Something":[]
|
||||
}
|
||||
|
||||
humidityHighLimit = 80.0
|
||||
humidityLowLimit = 5.0
|
||||
tempHighLimit= 95.0
|
||||
tempLowLimit= 50.0
|
||||
|
||||
def getChillerTonnage():
|
||||
return {
|
||||
"Common":[],
|
||||
"DH1":["[Ignition_Common_IO_Gtwy]DH1/OPC Tags/SATB1_DH1 Tonnage Demand"],
|
||||
"DH2":["[Ignition_Common_IO_Gtwy]DH2/OPC Tags/SATB1_DH2 Tonnage Demand"],
|
||||
"DH3":["[Ignition_Common_IO_Gtwy]DH3/OPC Tags/SATB1_DH3 Tonnage Demand"],
|
||||
"DH4":["[Ignition_Common_IO_Gtwy]DH4/OPC Tags/SATB1_DH4 Tonnage Demand"],
|
||||
"DH5":["[Ignition_Common_IO_Gtwy]DH5/OPC Tags/SATB1_DH5 Tonnage Demand"],
|
||||
}
|
||||
|
||||
def getPQMBase():
|
||||
return {
|
||||
"Common":["[Ignition_Common_IO_Gtwy]MPR1240/SATB1_MPR1240_M1_PQM",
|
||||
"[Ignition_Common_IO_Gtwy]MPR1240/SATB1_MPR1240_M2_PQM",
|
||||
"[Ignition_Common_IO_Gtwy]MPR1241/SATB1_MPR1241_M3_PQM",
|
||||
"[Ignition_Common_IO_Gtwy]MPR1241/SATB1_MPR1241_M4_PQM",
|
||||
"[Ignition_Common_IO_Gtwy]MPR1150/SATB1_MPR1150_M5_PQM",
|
||||
"[Ignition_Common_IO_Gtwy]MPR1150/SATB1_MPR1150_M6_PQM",
|
||||
"[Ignition_Common_IO_Gtwy]MPR1247/SATB1_MPR1247_M7_PQM",
|
||||
"[Ignition_Common_IO_Gtwy]MPR1247/SATB1_MPR1247_M8_PQM"],
|
||||
"DH1":["[Ignition_SATB1_IO1_default]ER1242/SWBD/SATB1_ER1242_PQM1",
|
||||
"[Ignition_SATB1_IO1_default]ER1243/SWBD/SATB1_ER1244_PQM1",
|
||||
"[Ignition_SATB1_IO1_default]ER1242/SWBD/SATB1_ER1245_PQM1",
|
||||
"[Ignition_SATB1_IO1_default]ER1242/SWBD/SATB1_ER1246_PQM1"],
|
||||
"DH2":["[Ignition_SATB1_IO2_default]ER1248/SWBD/SATB1_ER1248_PQM1",
|
||||
"[Ignition_SATB1_IO2_default]ER1249/SWBD/SATB1_ER1249_PQM1",
|
||||
"[Ignition_SATB1_IO2_default]ER1250/SWBD/SATB1_ER1250_PQM1",
|
||||
"[Ignition_SATB1_IO2_default]ER1251/SWBD/SATB1_ER1251_PQM1",
|
||||
"[Ignition_SATB1_IO2_default]ER1252/SWBD/SATB1_ER1252_PQM1"
|
||||
],
|
||||
"DH3":["[Ignition_SATB1_IO3_default]ER1155/SWBD/SATB1_ER1155_PQM1",
|
||||
"[Ignition_SATB1_IO3_default]ER1156/SWBD/SATB1_ER1156_PQM1",
|
||||
"[Ignition_SATB1_IO3_default]ER1157/SWBD/SATB1_ER1157_PQM1",
|
||||
"[Ignition_SATB1_IO3_default]ER1158/SWBD/SATB1_ER1158_PQM1",
|
||||
"[Ignition_SATB1_IO3_default]ER1159/SWBD/SATB1_ER1159_PQM1"
|
||||
],
|
||||
"DH4":["[Ignition_SATB1_IO4_default]ER1254/SWBD/SATB1_ER1254_PQM1",
|
||||
"[Ignition_SATB1_IO4_default]ER1255/SWBD/SATB1_ER1255_PQM1",
|
||||
"[Ignition_SATB1_IO4_default]ER1256/SWBD/SATB1_ER1256_PQM1",
|
||||
"[Ignition_SATB1_IO4_default]ER1257/SWBD/SATB1_ER1257_PQM1",
|
||||
"[Ignition_SATB1_IO4_default]ER1258/SWBD/SATB1_ER1258_PQM1"
|
||||
],
|
||||
"DH5":["[Ignition_SATB1_IO5_default]ER1161/SWBD/SATB1_ER1161_PQM1",
|
||||
"[Ignition_SATB1_IO5_default]ER1162/SWBD/SATB1_ER1162_PQM1",
|
||||
"[Ignition_SATB1_IO5_default]ER1163/SWBD/SATB1_ER1163_PQM1",
|
||||
"[Ignition_SATB1_IO5_default]ER1164/SWBD/SATB1_ER1164_PQM1",
|
||||
"[Ignition_SATB1_IO5_default]ER1165/SWBD/SATB1_ER1165_PQM1"
|
||||
],
|
||||
}
|
||||
|
||||
def getPDUBase():
|
||||
return {
|
||||
"Common":[],
|
||||
"DH1":["[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-1-1",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-1-2",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-1-3",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-1-4",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-2-1",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-2-2",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-2-3",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-2-4",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-3-1",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-3-2",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-3-3",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-3-4",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-4-1",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-4-2",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-4-3",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-4-4",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-5-1",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-5-2",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-5-3",
|
||||
"[Ignition_SATB1_IO1_default]GAL1261/SATB1_GAL1261_PDU_P1-5-4"],
|
||||
"DH2":[],
|
||||
"DH3":[],
|
||||
"DH4":[],
|
||||
"DH5":["[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-4-1",
|
||||
"[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-4-2",
|
||||
"[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-4-3",
|
||||
"[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-4-4",
|
||||
"[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-5-1",
|
||||
"[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-5-2",
|
||||
"[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-5-3",
|
||||
"[Ignition_SATB1_IO5_default]GAL1181/SATB1_GAL1181_PDU-P5-5-4"]
|
||||
}
|
||||
|
||||
def getGeneratorBase():
|
||||
return {
|
||||
"Common":[
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M1","[Ignition_Common_IO_Gtwy]MPR1240/SATB1_MPR1240_M1_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M2","[Ignition_Common_IO_Gtwy]MPR1240/SATB1_MPR1240_M2_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M3","[Ignition_Common_IO_Gtwy]MPR1241/SATB1_MPR1241_M3_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M4","[Ignition_Common_IO_Gtwy]MPR1241/SATB1_MPR1241_M4_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M5","[Ignition_Common_IO_Gtwy]MPR1150/SATB1_MPR1150_M5_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M6","[Ignition_Common_IO_Gtwy]MPR1150/SATB1_MPR1150_M6_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M7","[Ignition_Common_IO_Gtwy]MPR1247/SATB1_MPR1247_M7_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-M8","[Ignition_Common_IO_Gtwy]MPR1247/SATB1_MPR1247_M8_PQM"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P1-1","[Ignition_SATB1_IO1_default]ER1242/SWBD/SATB1_ER1242_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P1-2","[Ignition_SATB1_IO1_default]ER1243/SWBD/SATB1_ER1243_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P1-3","[Ignition_SATB1_IO1_default]ER1244/SWBD/SATB1_ER1244_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P1-4","[Ignition_SATB1_IO1_default]ER1245/SWBD/SATB1_ER1245_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P1-5","[Ignition_SATB1_IO1_default]ER1246/SWBD/SATB1_ER1246_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P3-1","[Ignition_SATB1_IO3_default]ER1155/SWBD/SATB1_ER1155_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P3-2","[Ignition_SATB1_IO3_default]ER1156/SWBD/SATB1_ER1156_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P3-3","[Ignition_SATB1_IO3_default]ER1157/SWBD/SATB1_ER1157_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P3-4","[Ignition_SATB1_IO3_default]ER1158/SWBD/SATB1_ER1158_PQM1"),
|
||||
("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_GEN-P3-5","[Ignition_SATB1_IO3_default]ER1159/SWBD/SATB1_ER1159_PQM1")],
|
||||
"DH1":[],
|
||||
"DH2":[("[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_GEN-P2-1","[Ignition_SATB1_IO2_default]ER1248/SWBD/SATB1_ER1248_PQM1"),
|
||||
("[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_GEN-P2-2","[Ignition_SATB1_IO2_default]ER1249/SWBD/SATB1_ER1249_PQM1"),
|
||||
("[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_GEN-P2-3","[Ignition_SATB1_IO2_default]ER1250/SWBD/SATB1_ER1250_PQM1"),
|
||||
("[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_GEN-P2-4","[Ignition_SATB1_IO2_default]ER1251/SWBD/SATB1_ER1251_PQM1"),
|
||||
("[Ignition_SATB1_IO2_default]Yard/SATB1_YARD_GEN-P2-5","[Ignition_SATB1_IO2_default]ER1252/SWBD/SATB1_ER1252_PQM1")],
|
||||
"DH3":[],
|
||||
"DH4":[("[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_GEN-P4-1","[Ignition_SATB1_IO4_default]ER1254/SWBD/SATB1_ER1254_PQM1"),
|
||||
("[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_GEN-P4-2","[Ignition_SATB1_IO4_default]ER1255/SWBD/SATB1_ER1255_PQM1"),
|
||||
("[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_GEN-P4-3","[Ignition_SATB1_IO4_default]ER1256/SWBD/SATB1_ER1256_PQM1"),
|
||||
("[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_GEN-P4-4","[Ignition_SATB1_IO4_default]ER1257/SWBD/SATB1_ER1257_PQM1"),
|
||||
("[Ignition_SATB1_IO4_default]Yard/SATB1_YARD_GEN-P4-5","[Ignition_SATB1_IO4_default]ER1258/SWBD/SATB1_ER1258_PQM1")],
|
||||
"DH5":[("[Ignition_SATB1_IO5_default]Yard/SATB1_YARD_GEN-P5-1","[Ignition_SATB1_IO5_default]ER1161/SWBD/SATB1_ER1161_PQM1"),
|
||||
("[Ignition_SATB1_IO5_default]Yard/SATB1_YARD_GEN-P5-2","[Ignition_SATB1_IO5_default]ER1162/SWBD/SATB1_ER1162_PQM1"),
|
||||
("[Ignition_SATB1_IO5_default]Yard/SATB1_YARD_GEN-P5-3","[Ignition_SATB1_IO5_default]ER1163/SWBD/SATB1_ER1163_PQM1"),
|
||||
("[Ignition_SATB1_IO5_default]Yard/SATB1_YARD_GEN-P5-4","[Ignition_SATB1_IO5_default]ER1164/SWBD/SATB1_ER1164_PQM1"),
|
||||
("[Ignition_SATB1_IO5_default]Yard/SATB1_YARD_GEN-P5-5","[Ignition_SATB1_IO5_default]ER1165/SWBD/SATB1_ER1165_PQM1")],
|
||||
}
|
||||
|
||||
def getUPSBase():
|
||||
"""
|
||||
Formatted in a way to group UPS to lines and lines to data hall
|
||||
"""
|
||||
return {
|
||||
"Common":[],
|
||||
"DH1":[("[Ignition_SATB1_IO1_default]ER1242/SATB1_ER1242_UPS-1", "[Ignition_SATB1_IO1_default]ER1242/SATB1_ER1242_UPS-2"),
|
||||
("[Ignition_SATB1_IO1_default]ER1243/SATB1_ER1243_UPS-1", "[Ignition_SATB1_IO1_default]ER1243/SATB1_ER1243_UPS-2"),
|
||||
("[Ignition_SATB1_IO1_default]ER1244/SATB1_ER1244_UPS-1","[Ignition_SATB1_IO1_default]ER1244/SATB1_ER1244_UPS-2"),
|
||||
("[Ignition_SATB1_IO1_default]ER1245/SATB1_ER1245_UPS-1","[Ignition_SATB1_IO1_default]ER1245/SATB1_ER1245_UPS-2"),
|
||||
("[Ignition_SATB1_IO1_default]ER1246/SATB1_ER1246_UPS-1","[Ignition_SATB1_IO1_default]ER1246/SATB1_ER1246_UPS-2")],
|
||||
"DH2":[("[Ignition_SATB1_IO2_default]ER1248/SATB1_ER1248_UPS-1","[Ignition_SATB1_IO2_default]ER1248/SATB1_ER1248_UPS-2"),
|
||||
("[Ignition_SATB1_IO2_default]ER1249/SATB1_ER1249_UPS-1","[Ignition_SATB1_IO2_default]ER1249/SATB1_ER1249_UPS-2"),
|
||||
("[Ignition_SATB1_IO2_default]ER1250/SATB1_ER1250_UPS-1","[Ignition_SATB1_IO2_default]ER1250/SATB1_ER1250_UPS-2"),
|
||||
("[Ignition_SATB1_IO2_default]ER1251/SATB1_ER1251_UPS-1","[Ignition_SATB1_IO2_default]ER1251/SATB1_ER1251_UPS-2"),
|
||||
("[Ignition_SATB1_IO2_default]ER1252/SATB1_ER1252_UPS-1","[Ignition_SATB1_IO2_default]ER1252/SATB1_ER1252_UPS-1")],
|
||||
"DH3":[("[Ignition_SATB1_IO3_default]ER1155/SATB1_ER1155_UPS-1","[Ignition_SATB1_IO3_default]ER1155/SATB1_ER1155_UPS-2"),
|
||||
("[Ignition_SATB1_IO3_default]ER1156/SATB1_ER1156_UPS-1","[Ignition_SATB1_IO3_default]ER1156/SATB1_ER1156_UPS-2"),
|
||||
("[Ignition_SATB1_IO3_default]ER1157/SATB1_ER1157_UPS-1","[Ignition_SATB1_IO3_default]ER1157/SATB1_ER1157_UPS-2"),
|
||||
("[Ignition_SATB1_IO3_default]ER1158/SATB1_ER1158_UPS-1","[Ignition_SATB1_IO3_default]ER1158/SATB1_ER1158_UPS-2"),
|
||||
("[Ignition_SATB1_IO3_default]ER1159/SATB1_ER1159_UPS-1", "[Ignition_SATB1_IO3_default]ER1159/SATB1_ER1159_UPS-2")],
|
||||
"DH4":[("[Ignition_SATB1_IO4_default]ER1254/SATB1_ER1254_UPS-1","[Ignition_SATB1_IO4_default]ER1254/SATB1_ER1254_UPS-2"),
|
||||
("[Ignition_SATB1_IO4_default]ER1255/SATB1_ER1255_UPS-1","[Ignition_SATB1_IO4_default]ER1255/SATB1_ER1255_UPS-2"),
|
||||
("[Ignition_SATB1_IO4_default]ER1256/SATB1_ER1256_UPS-1","[Ignition_SATB1_IO4_default]ER1256/SATB1_ER1256_UPS-2"),
|
||||
("[Ignition_SATB1_IO4_default]ER1257/SATB1_ER1257_UPS-1","[Ignition_SATB1_IO4_default]ER1257/SATB1_ER1257_UPS-2"),
|
||||
("[Ignition_SATB1_IO4_default]ER1258/SATB1_ER1258_UPS-1","[Ignition_SATB1_IO4_default]ER1258/SATB1_ER1258_UPS-2")],
|
||||
"DH5":[("[Ignition_SATB1_IO5_default]ER1161/SATB1_ER1161_UPS-1", "[Ignition_SATB1_IO5_default]ER1161/SATB1_ER1161_UPS-2"),
|
||||
("[Ignition_SATB1_IO5_default]ER1162/SATB1_ER1162_UPS-1","[Ignition_SATB1_IO5_default]ER1162/SATB1_ER1162_UPS-2"),
|
||||
("[Ignition_SATB1_IO5_default]ER1163/SATB1_ER1163_UPS-1","[Ignition_SATB1_IO5_default]ER1163/SATB1_ER1163_UPS-2"),
|
||||
("[Ignition_SATB1_IO5_default]ER1164/SATB1_ER1164_UPS-1", "[Ignition_SATB1_IO5_default]ER1164/SATB1_ER1164_UPS-2"),
|
||||
("[Ignition_SATB1_IO5_default]ER1165/SATB1_ER1165_UPS-1", "[Ignition_SATB1_IO5_default]ER1165/SATB1_ER1165_UPS-2")]
|
||||
}
|
||||
def getUtilitySES():
|
||||
return {
|
||||
"Yard": ["[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_SES_1A/PQM_Main",
|
||||
"[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_SES_1B/PQM_Main"],
|
||||
"Common":[],
|
||||
"DH1":[],
|
||||
"DH2":[],
|
||||
"DH3":[],
|
||||
"DH4":[],
|
||||
"DH5":[],
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "4be4bd697ca3e6827d3cc1dd7b20cb29b02d3e664a9f8534754b1e4c3a2eaf03",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:15Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
from datetime import datetime
|
||||
def getBaseTagPaths(collection, location=None):
|
||||
if location is not None:
|
||||
return collection[location]
|
||||
else:
|
||||
# return [x for v in collection.values() for x in v]
|
||||
return [value for key, values in collection.items() if key in ["Common","DH1","DH2","DH3","DH4","DH5"] for value in values]
|
||||
|
||||
|
||||
def getHistory(paths, startDate, endDate, customArgs={}):
|
||||
"""
|
||||
Generic get history
|
||||
Args:
|
||||
paths: [] of strings/tagpaths
|
||||
startDate: date start of range
|
||||
endDate: date end of range
|
||||
customArgs: dictionary of custom values if needed
|
||||
returns:
|
||||
PyDataSet
|
||||
"""
|
||||
intervalMin = customArgs.get("intervalMinutes",1)
|
||||
noInterpol = customArgs.get("noInterpolation",True)
|
||||
ignoreBad = customArgs.get("ignoreBadQuality",True)
|
||||
aggMode = customArgs.get("aggregationMode","LastValue")
|
||||
|
||||
histDS = system.tag.queryTagHistory(paths, startDate, endDate, aggregationMode = aggMode, intervalMinutes= intervalMin,noInterpolation= noInterpol, ignoreBadQuality= ignoreBad )
|
||||
# print "common.util.getHist", histDS.getRowCount(), paths
|
||||
return system.dataset.toPyDataSet(histDS)
|
||||
|
||||
|
||||
def generateSumCol(hist, sumColName="SumRealPwr"):
|
||||
allHeaders = system.dataset.getColumnHeaders(hist)
|
||||
|
||||
sumCol = []
|
||||
for row in hist:
|
||||
try:
|
||||
sumCol.append(sum([row[h] for h in allHeaders[1:] if row[h] is not None]) if len(allHeaders)>1 else 0)
|
||||
except:
|
||||
sumCol.append(0)
|
||||
|
||||
return system.dataset.toPyDataSet(system.dataset.addColumn(hist, sumCol, sumColName, float))
|
||||
|
||||
def averageIntervalData(histDS, interval=15):
|
||||
"""
|
||||
Average values over time based on minute per minute data.
|
||||
First column should be t_stamp
|
||||
"""
|
||||
timeUnitStr = ""
|
||||
GroupDict= {} # represents {"datetime":{"col1":[], "col2":[]...}}
|
||||
histDsCols = system.dataset.getColumnHeaders(histDS)[1:]
|
||||
resultData = []
|
||||
for i,row in enumerate(histDS):
|
||||
if i%interval == 0:
|
||||
timeUnitStr = system.date.format(row["t_stamp"],"yyyy-MM-dd HH:mm")
|
||||
GroupDict.setdefault(timeUnitStr, {})
|
||||
GroupDict[timeUnitStr]={col:[] for col in histDsCols}
|
||||
# GroupDict[quarterHrStr].append(row["SumRealPwr"])
|
||||
for col in histDsCols:
|
||||
GroupDict[timeUnitStr][col].append(row[col] if row[col] is not None else 0)
|
||||
sortedDateKeys = sorted(GroupDict.keys(), key=lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M'))
|
||||
for dt_str in sortedDateKeys:
|
||||
oneRow = [system.date.parse(dt_str, "yyyy-MM-dd HH:mm")]+ [sum(GroupDict[dt_str][col])/len(GroupDict[dt_str][col]) for col in histDsCols]
|
||||
resultData.append(oneRow)
|
||||
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(["t_stamp"]+histDsCols, resultData))
|
||||
|
||||
|
||||
def mergeHistorianData(ds1, ds2):
|
||||
"""
|
||||
Expects 2 sets of historian data, sanity check . t_stamp should be column index 0, so we only need one
|
||||
"""
|
||||
if len(ds1) != len(ds2):
|
||||
raise Exception("Mismatch on both datasets")
|
||||
else:
|
||||
ds1Header = system.dataset.getColumnHeaders(ds1)
|
||||
ds2Header = system.dataset.getColumnHeaders(ds2)[1:]
|
||||
|
||||
newHeader = ds1Header+ds2Header
|
||||
|
||||
allRows = []
|
||||
for i,row in enumerate(ds1):
|
||||
newRow = [c for c in row] + ([c for c in ds2[i]][1:])
|
||||
allRows.append(newRow)
|
||||
# print len(newHeader), len(allRows[0])
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(newHeader, allRows))
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "1ea8a1d8875f23df70e875640630463a30e363ae5f65953b6e8d886ac18b5251",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
from datetime import datetime
|
||||
udtInstPath = "[Ignition_Common_IO_Gtwy]Roof/SATB1_RF_RTU01"
|
||||
|
||||
|
||||
def getMeasurementPaths():
|
||||
return ["%s/Outside Air Dewpoint"%udtInstPath,
|
||||
"%s/Outside Air Temperature"%udtInstPath,
|
||||
"%s/Outside Air Humidity"%udtInstPath]
|
||||
|
||||
def getRawDataMeasurement(startDate, endDate, customArgs= {}):
|
||||
tagpaths = getMeasurementPaths()
|
||||
hist = reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={})
|
||||
|
||||
|
||||
return hist
|
||||
|
||||
def getRawChillerDemand(startDate, endDate, location, customArgs= {}):
|
||||
tagpaths = reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getChillerTonnage(), location)
|
||||
tagpaths.sort()
|
||||
hist = reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={})
|
||||
|
||||
return hist
|
||||
|
||||
def calculateValues(data):
|
||||
"""
|
||||
Calculate the Daily Average of each sensor's measurement
|
||||
"""
|
||||
data = system.dataset.toPyDataSet(data)
|
||||
curHeaders = system.dataset.getColumnHeaders(data)
|
||||
res = {"ResultTemp":None, "ResultHumidity":None}
|
||||
dailyGroup= {}
|
||||
allResultData = {}
|
||||
allResultDS = {}
|
||||
allAvgs = []
|
||||
|
||||
for row in data:
|
||||
dayStr = system.date.format(row["t_stamp"],"yyyy-MM-dd")
|
||||
dailyGroup.setdefault(dayStr, {})
|
||||
dailyGroup[dayStr].setdefault("OATemp", [])
|
||||
dailyGroup[dayStr].setdefault("OADew", [])
|
||||
dailyGroup[dayStr].setdefault("OAHum", [])
|
||||
|
||||
dailyGroup[dayStr]["OADew"].append(row[curHeaders[1]])
|
||||
dailyGroup[dayStr]["OATemp"].append(row[curHeaders[2]])
|
||||
dailyGroup[dayStr]["OAHum"].append(row[curHeaders[3]])
|
||||
|
||||
sortedDateKeys = sorted(dailyGroup.keys(), key=lambda x: datetime.strptime(x, '%Y-%m-%d'))
|
||||
temperatureRows= []
|
||||
humidityRows= []
|
||||
for date_str in sortedDateKeys:
|
||||
try:
|
||||
dpAvg = sum(dailyGroup[date_str]["OADew"])/len(dailyGroup[date_str]["OADew"])
|
||||
tempAvg = sum(dailyGroup[date_str]["OATemp"])/len(dailyGroup[date_str]["OATemp"])
|
||||
humAvg = sum(dailyGroup[date_str]["OAHum"])/len(dailyGroup[date_str]["OAHum"])
|
||||
except:
|
||||
dpAvg = 0
|
||||
tempAvg = 0
|
||||
humAvg = 0
|
||||
|
||||
temperatureRows.append([system.date.parse(date_str, "yyyy-MM-dd"), dpAvg, tempAvg])
|
||||
humidityRows.append([system.date.parse(date_str, "yyyy-MM-dd"), humAvg])
|
||||
|
||||
res["ResultTemp"] = system.dataset.toDataSet(["Date","Dewpoint","Temperature"], temperatureRows)
|
||||
res["ResultHumidity"] = system.dataset.toDataSet(["Date","Humidity"], humidityRows)
|
||||
return res
|
||||
|
||||
def DoAll(startDate,endDate):
|
||||
return calculateValues(getRawDataMeasurement(startDate,endDate))
|
||||
|
||||
def DoChillerDemand(startDate, endDate, interval= 60, locations=None):
|
||||
histDS = reports.MBR.cooling.getRawChillerDemand(startDate, endDate, locations, customArgs= {})
|
||||
histDS = reports.MBR.common.util.averageIntervalData(histDS, interval)
|
||||
histDS = reports.MBR.common.util.generateSumCol(histDS, sumColName="Sum Tonnage")
|
||||
|
||||
return {"histDS":histDS}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "9cd76c3bcdb531699c938b8ca0b32e5aa82ec7aff5e4f3437e24029a40bd0320",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
Logger = system.util.getLogger("reports.MBR.coolingdata")
|
||||
|
||||
def test1_0():
|
||||
res={}
|
||||
endDate= system.date.midnight(system.date.now())
|
||||
startDate = system.date.addDays(endDate, -2)
|
||||
tempDS, humDS = reports.MBR.coolingdata.getRawDataMeasurement(startDate, endDate, "DH1", customArgs= {"noInterpolation":False})
|
||||
# res["tempDS"]= tempDS
|
||||
# res["humDS"] = humDS
|
||||
# group the sensors by aisle then perform an interval average
|
||||
tempDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(tempDS, "Temp"), 60)
|
||||
humDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(humDS, "Humidity"), 60)
|
||||
|
||||
|
||||
tempSummaryDS = reports.MBR.coolingdata.generateSummaryColumnDS(tempDS, "Temp")
|
||||
humiditySummaryDS = reports.MBR.coolingdata.generateSummaryColumnDS(humDS, "Humidity")
|
||||
summaryDS = combineTempHumidity(tempSummaryDS, humiditySummaryDS)
|
||||
del tempSummaryDS
|
||||
del humiditySummaryDS
|
||||
|
||||
|
||||
measurementDS = reports.MBR.common.util.mergeHistorianData(combineTempHumidity(tempDS, humDS),summaryDS)
|
||||
res["measurementDS"]=measurementDS
|
||||
res["summaryDS"]=summaryDS
|
||||
return res
|
||||
|
||||
|
||||
def genSummCols(ds, snsrType, aggRow):
|
||||
if aggRow:
|
||||
return reports.MBR.coolingdata.generateSummaryColumnDS(ds, snsrType)
|
||||
else:
|
||||
return reports.MBR.coolingdata.generateRangeSummaryCol(ds, snsrType)
|
||||
def doReport(startDate, endDate, locations, intervalTime = 60, aggRow= True):
|
||||
"""
|
||||
Args:
|
||||
aggRow boolean, true: get the min/max/avg on row, false: get the min max over the range, and avg per row
|
||||
"""
|
||||
res={}
|
||||
customArgs= {"noInterpolation":False}
|
||||
tempDS, humDS = None, None
|
||||
if locations is None:
|
||||
tempDS, humDS = reports.MBR.coolingdata.getRawDataMeasurement(startDate, endDate, None,customArgs)
|
||||
else:
|
||||
for loc in locations:
|
||||
ds1, ds2 = getRawDataMeasurement(startDate, endDate, loc, customArgs)
|
||||
if tempDS is None and humDS is None:
|
||||
tempDS = ds1
|
||||
humDS = ds2
|
||||
else:
|
||||
tempDS= reports.MBR.common.util.mergeHistorianData(tempDS, ds1)
|
||||
humDS= reports.MBR.common.util.mergeHistorianData(humDS, ds1)
|
||||
# res["tempDS"]= tempDS
|
||||
# res["humDS"] = humDS
|
||||
# group the sensors by aisle then perform an interval average
|
||||
tempDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(tempDS, "Temp"), intervalTime)
|
||||
humDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(humDS, "Humidity"), intervalTime)
|
||||
|
||||
|
||||
tempSummaryDS = genSummCols(tempDS, "Temp", aggRow)
|
||||
humiditySummaryDS = genSummCols(humDS, "Humidity", aggRow)
|
||||
summaryDS = combineTempHumidity(tempSummaryDS, humiditySummaryDS)
|
||||
del tempSummaryDS
|
||||
del humiditySummaryDS
|
||||
|
||||
|
||||
measurementDS = addStaticLimits(reports.MBR.common.util.mergeHistorianData(combineTempHumidity(tempDS, humDS),summaryDS))
|
||||
res["measurementDS"]=measurementDS
|
||||
res["summaryDS"]=summaryDS
|
||||
return res
|
||||
|
||||
def doReport2(startDate, endDate, locations, intervalTime = 60, aggRow= False):
|
||||
"""
|
||||
Args:
|
||||
aggRow boolean, true: get the min/max/avg on row, false: get the min max over the range, and avg per row
|
||||
"""
|
||||
tSt = time.time()
|
||||
res={}
|
||||
customArgs= {"noInterpolation":False}
|
||||
superTempDS, superHumDS, tempDS, humDS = None, None, None, None
|
||||
if locations is None:
|
||||
locations = ["DH1","DH2","DH3","DH4","DH5"]
|
||||
summaryRows = []
|
||||
|
||||
|
||||
for loc in locations:
|
||||
ds1, ds2 = getRawDataMeasurement(startDate, endDate, loc, customArgs)
|
||||
tempDS = ds1
|
||||
humDS = ds2
|
||||
if superTempDS is None and superHumDS is None:
|
||||
superTempDS = ds1
|
||||
superHumDS = ds2
|
||||
else:
|
||||
superTempDS= reports.MBR.common.util.mergeHistorianData(superTempDS, ds1)
|
||||
superHumDS= reports.MBR.common.util.mergeHistorianData(superHumDS, ds2)
|
||||
tempDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(tempDS, "Temp"), intervalTime)
|
||||
humDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(humDS, "Humidity"), intervalTime)
|
||||
|
||||
|
||||
|
||||
tempSummaryDS = genSummCols(tempDS, "Temp", aggRow)
|
||||
humiditySummaryDS = genSummCols(humDS, "Humidity", aggRow)
|
||||
summaryDS = combineTempHumidity(tempSummaryDS, humiditySummaryDS)
|
||||
del tempSummaryDS
|
||||
del humiditySummaryDS
|
||||
summaryRows.append([loc.replace("DH", "Datahall "), system.dataset.toDataSet(summaryDS) ])
|
||||
print "elapsed 1: ", time.time() - tSt
|
||||
|
||||
# group the sensors by aisle then perform an interval average
|
||||
print "elapsed 2: ", time.time()- tSt
|
||||
superTempDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(superTempDS, "Temp"), intervalTime)
|
||||
superHumDS = reports.MBR.common.util.averageIntervalData(groupColumnLocation(superHumDS, "Humidity"), intervalTime)
|
||||
tempSummaryDS = genSummCols(superTempDS, "Temp", aggRow)
|
||||
humiditySummaryDS = genSummCols(superHumDS, "Humidity", aggRow)
|
||||
summaryDS = combineTempHumidity(tempSummaryDS, humiditySummaryDS)
|
||||
measurementDS = addStaticLimits(reports.MBR.common.util.mergeHistorianData(combineTempHumidity(superTempDS, superHumDS),summaryDS))
|
||||
print "elapsed 3: ", time.time()- tSt
|
||||
|
||||
res["summaryDS"]=summaryDS
|
||||
res["measurementDS"]=measurementDS
|
||||
res["locSummaryDS"] = system.dataset.toDataSet(["Location", "data"], summaryRows)
|
||||
return res
|
||||
# res["summaryDS"]=system.dataset.toDataSet([],summaryDS)
|
||||
def getRawDataMeasurement(startDate, endDate, location, customArgs= {}):
|
||||
"""
|
||||
Get the history 1-minute interval for the relevant location sensors
|
||||
Args:
|
||||
startDate: datetime
|
||||
endDate: datetime
|
||||
location: str DH1, DH2..
|
||||
customArgs: optional overrides for the history retrieval
|
||||
Returns:
|
||||
tuple (temp Dataset, humidity dataset)
|
||||
"""
|
||||
logger = Logger.createSubLogger("getRawDataMeasurement")
|
||||
stTime= time.time()
|
||||
tempTags = reports.MBR.common.coolingTags.generateTags(location, "temp")
|
||||
humidityTags = reports.MBR.common.coolingTags.generateTags(location, "humidity")
|
||||
|
||||
tempDS = reports.MBR.common.util.getHistory(tempTags, startDate, endDate, customArgs)
|
||||
humidityDS = reports.MBR.common.util.getHistory(humidityTags, startDate, endDate, customArgs)
|
||||
logger.debug("complete duration: %s"%(time.time()-stTime))
|
||||
|
||||
return tempDS, humidityDS
|
||||
|
||||
|
||||
|
||||
def groupColumnLocation(histDS, sensorType):
|
||||
"""
|
||||
Take the full range of tag columns and group them by location and average the value
|
||||
ex. [Ignition_Common_IO_Gtwy]DH1/SATB1_DH1_PNL24_C1_HT01/Val
|
||||
[Ignition_Common_IO_Gtwy]DH1/SATB1_DH1_PNL24_C1_HT02/Val
|
||||
[Ignition_Common_IO_Gtwy]DH1/SATB1_DH1_PNL24_C1_HT03/Val
|
||||
becomes DH1_C1
|
||||
Args:
|
||||
histDS: pyds of history
|
||||
sensorType: str Temp/Humidity
|
||||
Returns:
|
||||
py dataset
|
||||
"""
|
||||
def groupColNames(colNames):
|
||||
colMap = OrderedDict()
|
||||
for name in colNames:
|
||||
|
||||
cNameParts = name.split("/")[1].split("_")
|
||||
gName = "%s_%s %s"%(cNameParts[1], cNameParts[3], sensorType)
|
||||
colMap.setdefault(gName, [])
|
||||
colMap[gName].append(name)
|
||||
return colMap
|
||||
|
||||
oColNames = system.dataset.getColumnHeaders(histDS)[1:]
|
||||
colMap = groupColNames(oColNames)
|
||||
newCols = colMap.keys()
|
||||
|
||||
# now let's average across the new names
|
||||
allRows = []
|
||||
for row in histDS:
|
||||
oneRow = [row["t_stamp"]]
|
||||
for grp in newCols:
|
||||
groupVals = [row[c] for c in colMap[grp]]
|
||||
try:
|
||||
oneRow.append(sum(groupVals)/len(groupVals))
|
||||
except:
|
||||
oneRow.append(-1.0)
|
||||
|
||||
allRows.append(oneRow)
|
||||
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(["t_stamp"]+newCols , allRows))
|
||||
|
||||
def combineTempHumidity(tempDS, humidityDS):
|
||||
"""
|
||||
Build the custom column sort order C1 Humidity, C1 Temp, C2 Humidity, etc..
|
||||
Args:
|
||||
tempDS: pyDataset
|
||||
humidityDS: humidityDS
|
||||
Returns:
|
||||
pydataset
|
||||
"""
|
||||
allRows = []
|
||||
header = ["t_stamp"]
|
||||
tempHeaders = system.dataset.getColumnHeaders(tempDS)
|
||||
humHeaders = system.dataset.getColumnHeaders(humidityDS)
|
||||
|
||||
for rCnt, row in enumerate(humidityDS):
|
||||
newRow = [row["t_stamp"]]
|
||||
for cCnt in range(1, len(row)):
|
||||
if rCnt == 0:
|
||||
header.extend([humHeaders[cCnt], tempHeaders[cCnt]])
|
||||
newRow.extend([row[cCnt], tempDS[rCnt][cCnt]])
|
||||
allRows.append(newRow)
|
||||
# print header, len(header), len(allRows[0])
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(header, allRows))
|
||||
|
||||
|
||||
def generateSummaryColumnDS(ds1, sensorType):
|
||||
"""
|
||||
Receive the grouped dataset and calculate the Min/Max/Avg/Limit for each row
|
||||
Args:
|
||||
ds1: pydataset grouped by sensors
|
||||
sensorType: str (Temp or Humidity)
|
||||
Returns:
|
||||
pydataset with columns t_stamp, Max , Avg, Min
|
||||
"""
|
||||
header = ["t_stamp", "Max %s"%(sensorType), "Avg %s"%(sensorType), "Min %s"%(sensorType)]
|
||||
allRows = []
|
||||
for row in ds1:
|
||||
rowVals= [c for c in row[1:]]
|
||||
allRows.append([row["t_stamp"], max(rowVals), sum(rowVals)/len(rowVals), min(rowVals)])
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(header, allRows))
|
||||
|
||||
|
||||
def generateRangeSummaryCol(ds1, sensorType):
|
||||
"""
|
||||
Receive the grouped dataset and calculate the Min/Max/Avg/Limit for the entire range
|
||||
Args:
|
||||
ds1: pydataset grouped by sensors
|
||||
sensorType: str (Temp or Humidity)
|
||||
Returns:
|
||||
pydataset with columns t_stamp, Max , Avg, Min
|
||||
"""
|
||||
header = ["t_stamp", "Max %s"%(sensorType), "Avg %s"%(sensorType), "Min %s"%(sensorType)]
|
||||
allRows = []
|
||||
vals = []
|
||||
for row in ds1:
|
||||
vals.extend([v for v in row[1:]])
|
||||
rowVals= [c for c in row[1:]]
|
||||
allRows.append([row["t_stamp"], None, sum(rowVals)/len(rowVals), None])
|
||||
valMin = min(vals)
|
||||
valMax = max(vals)
|
||||
for sRow in allRows:
|
||||
sRow[1] = valMax
|
||||
sRow[3] = valMin
|
||||
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(header, allRows))
|
||||
|
||||
def addStaticLimits(ds):
|
||||
limits= []
|
||||
limits.append( [reports.MBR.common.static.humidityHighLimit for i in range(len(ds))])
|
||||
limits.append( [reports.MBR.common.static.humidityLowLimit for i in range(len(ds))])
|
||||
limits.append( [reports.MBR.common.static.tempHighLimit for i in range(len(ds))])
|
||||
limits.append( [reports.MBR.common.static.tempLowLimit for i in range(len(ds))])
|
||||
|
||||
for i,cName in enumerate(["Hum High Lim", "Hum Low Lim", "Temp High Lim", "Temp Low Lim"]):
|
||||
ds = system.dataset.addColumn(ds, limits[i], cName, float)
|
||||
|
||||
return system.dataset.toPyDataSet(ds)
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "6695e2ab7dfb639ba559e4a9681ab44d182497d1e1086a6e3601b1913be684e9",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
from datetime import datetime
|
||||
import re
|
||||
def getTagPaths(location=None):
|
||||
"""
|
||||
Get all the relvant tagpaths needed for the historian query
|
||||
"""
|
||||
baseTagPaths = reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getUPSBase(), location)
|
||||
return baseTagPaths
|
||||
|
||||
def getPowerPaths(location):
|
||||
paths = []
|
||||
for ups1path, ups2path in getTagPaths(location):
|
||||
paths.extend(["%s/Total kW"%ups1path, "%s/Total kW"%ups2path])
|
||||
return paths
|
||||
def getPowerHist(startDate,endDate, location=None):
|
||||
tagpaths = getPowerPaths(location)
|
||||
|
||||
hist = reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={})
|
||||
return reports.MBR.common.util.generateSumCol(hist, sumColName="SumTotalkW"), tagpaths
|
||||
|
||||
|
||||
def averageHistData(histData, inclColumns, interval=None):
|
||||
"""
|
||||
Calculate the Average, Min Average, Max Average
|
||||
Args:
|
||||
data: historian data, expected in minute intervals
|
||||
inclColumns: list of strings reprsenting the tagpath/column name from queryTagHistory
|
||||
interval: grouping by minutes, None if daily
|
||||
Returns:
|
||||
dictionary containing calculated data
|
||||
"""
|
||||
data = system.dataset.toPyDataSet(histData)
|
||||
res = {"MinAvg":-1,"MaxAvg":-1,"ResultData":None}
|
||||
dailyGroup= {}
|
||||
resultData = []
|
||||
allAvgs = []
|
||||
def getUPSName(inclNames):
|
||||
newNames = []
|
||||
for name in inclNames:
|
||||
newNames.append(name.split("_")[-1].split("/")[0])
|
||||
return newNames
|
||||
if interval is None:
|
||||
for row in data:
|
||||
# dayStr = system.date.format(system.date.fromMillis(row["t_stamp"]),"yyyy-MM-dd")
|
||||
dayStr = system.date.format(row["t_stamp"],"yyyy-MM-dd")
|
||||
dailyGroup.setdefault(dayStr,{c:[] for c in inclColumns})
|
||||
for c in inclColumns:
|
||||
dailyGroup[dayStr][c].append(row[c])
|
||||
|
||||
|
||||
|
||||
sortedDateKeys = sorted(dailyGroup.keys(), key=lambda x: datetime.strptime(x, '%Y-%m-%d'))
|
||||
for date_str in sortedDateKeys:
|
||||
oneRow= [system.date.parse(date_str, "yyyy-MM-dd")]
|
||||
for c in inclColumns:
|
||||
cleanValues = [v for v in dailyGroup[date_str][c] if v is not None]
|
||||
dailyAvg = sum(cleanValues)/len(cleanValues) if len(cleanValues) > 0 else 0
|
||||
allAvgs.append(dailyAvg)
|
||||
oneRow.append(dailyAvg)
|
||||
resultData.append(oneRow)
|
||||
|
||||
else:
|
||||
quarterHrStr = ""
|
||||
for i,row in enumerate(data):
|
||||
if i%interval == 0:
|
||||
quarterHrStr = system.date.format(row["t_stamp"],"yyyy-MM-dd HH:mm")
|
||||
dailyGroup.setdefault(quarterHrStr,{c:[] for c in inclColumns})
|
||||
for c in inclColumns:
|
||||
dailyGroup[quarterHrStr][c].append(row[c])
|
||||
sortedDateKeys = sorted(dailyGroup.keys(), key=lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M'))
|
||||
for dt_str in sortedDateKeys:
|
||||
oneRow = [system.date.parse(dt_str, "yyyy-MM-dd HH:mm")]
|
||||
for c in inclColumns:
|
||||
# print dailyGroup[dt_str]
|
||||
cleanValues = [v for v in dailyGroup[dt_str][c] if v is not None]
|
||||
dtAvg = sum(cleanValues)/len(cleanValues) if len(cleanValues) > 0 else 0
|
||||
allAvgs.append(dtAvg)
|
||||
oneRow.append(dtAvg)
|
||||
resultData.append(oneRow)
|
||||
|
||||
res["MinAvg"] = min(allAvgs) if len(allAvgs) > 0 and not all(x == 0 for x in allAvgs) else -1
|
||||
res["MaxAvg"] = max(allAvgs) if len(allAvgs) > 0 and not all(x == 0 for x in allAvgs) else 1
|
||||
res["SimpAvg"] = sum(allAvgs)/len(allAvgs) if len(allAvgs) > 0 else 0
|
||||
res["Paths"] = inclColumns
|
||||
|
||||
# we're going to inject two new columns to resultData
|
||||
for row in resultData:
|
||||
row.extend([res["MinAvg"], res["MaxAvg"]])
|
||||
|
||||
res["ResultDS"] = system.dataset.toDataSet(["Date"]+["Unit%s"%(i+1) for i in range(len(inclColumns))]+["Min", "Max"], resultData)
|
||||
# res["RawDS"] = data
|
||||
return res
|
||||
|
||||
def getEqNamesByLoc(location):
|
||||
mEqPaths = []
|
||||
for ups1path, ups2path in getTagPaths(location):
|
||||
mEqPaths.extend(["%s/Meta/EqName"%ups1path, "%s/Meta/EqName"%ups2path])
|
||||
return [qv.value for qv in system.tag.readBlocking(mEqPaths)]
|
||||
|
||||
def getEqNamesByPath(paths):
|
||||
mEqPaths = ["/".join(p.split("/")[:-1]) +"/Meta/EqName" for p in paths]
|
||||
return [qv.value for qv in system.tag.readBlocking(mEqPaths)]
|
||||
|
||||
def processAverage(startDate, endDate, location, interval=None):
|
||||
finalRes= {}
|
||||
def getUPSName(tagpath):
|
||||
return tagpath.split("_")[-1].split("/")[0][:-2] if "-"in tagpath else tagpath.split("_")[-1].split("/")[0][:-1]
|
||||
def getRoomName(tagpath):
|
||||
# return tagpath.split("/")[1].split("_")[1]
|
||||
if "]" in tagpath:
|
||||
return tagpath.split("/")[0].split("]")[1]
|
||||
else:
|
||||
return tagpath.split("/")[0]
|
||||
|
||||
|
||||
histData, rawTagPaths = getPowerHist(startDate,endDate, location)
|
||||
header = system.dataset.getColumnHeaders(histData)
|
||||
# clone the header for actual tagpaths
|
||||
rawTagPaths = [""]+rawTagPaths
|
||||
# print header
|
||||
allRows = []
|
||||
newHeader = ["ChartName","MinAvg","MaxAvg","ChartDS", "SimpAvg"]
|
||||
|
||||
res = averageHistData(histData, ["SumTotalkW"], interval)
|
||||
allRows.append(["Data Hall Total Loading", res["MinAvg"], res["MaxAvg"], res["ResultDS"], res["SimpAvg"]])
|
||||
finalRes["TotalLoading"] = system.dataset.toDataSet(newHeader, allRows)
|
||||
|
||||
allRows= []
|
||||
|
||||
# Format the result
|
||||
for i in range(1, len(header)-2, 2): # Start at index 1, step by 2 to get pairs
|
||||
tag1, tag2 = header[i], header[i+1]
|
||||
res = averageHistData(histData, [tag1,tag2], interval)
|
||||
eq1,eq2 = reports.MBR.criticalpower.getEqNamesByPath([rawTagPaths[i], rawTagPaths[i+1]])
|
||||
allRows.append([getRoomName(tag1)+" Loading", res["MinAvg"], res["MaxAvg"], res["ResultDS"], res["SimpAvg"], getUPSName(tag1), eq1, eq2])
|
||||
|
||||
finalRes["reportDS"] = system.dataset.toDataSet(newHeader+["ChartCategory", "Eq1", "Eq2"], allRows)
|
||||
finalRes["rawDS"] = histData
|
||||
# finalRes["NameLists"] = reports.MBR.criticalpower.getEqNames(location)
|
||||
|
||||
return finalRes
|
||||
|
||||
def doDailyReport(startDate, endDate, location=None):
|
||||
return processAverage(startDate, endDate, location, None)
|
||||
|
||||
def doReportInterval(startDate, endDate, location=None, interval=15):
|
||||
return processAverage(startDate, endDate, location, interval)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "837a8bf93a7e251a59f8441a16ae27a5a8b12ab148439017613fb4fbbe9dbed6",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
from datetime import datetime
|
||||
|
||||
def getRawDataTotalLoad(startDate, endDate, location, customArgs= {}):
|
||||
return reports.MBR.totalpower.getRawDataTotalLoad(startDate, endDate, location, customArgs)
|
||||
|
||||
def getRawDataCriticalLoad(startDate, endDate, location, customArgs= {}):
|
||||
return reports.MBR.totalpower.getRawDataCriticalLoad(startDate, endDate, location, customArgs= {})
|
||||
|
||||
|
||||
def stripHistColumns(histDS, colsToKeep=[]):
|
||||
"""
|
||||
Support function to remove standalone columns
|
||||
"""
|
||||
histHeaders = system.dataset.getColumnHeaders(histDS)
|
||||
newHeader = [histHeaders[0]] + colsToKeep
|
||||
|
||||
allRows = []
|
||||
for row in histDS:
|
||||
allRows.append([row[c] for c in newHeader])
|
||||
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet((newHeader, allRows)))
|
||||
|
||||
def processCalcPeakAvg(histDS, keyCol, colNames, interval):
|
||||
"""
|
||||
Calculate peak and avg by interval
|
||||
Args:
|
||||
histDS: history dataset to process
|
||||
keyCol: identify the key column to calculate from
|
||||
colNames: new column names for peak and avg
|
||||
interval: histDS should be in minute intervals, how many rows per grouping
|
||||
Returns:
|
||||
dataset of t_stamp, Peak<keyCol>, Avg<keyCol>
|
||||
GroupDict of interval raw values
|
||||
"""
|
||||
timeUnitStr = ""
|
||||
GroupDict= {} # represents {"datetime":{"col1":[], "col2":[]...}}
|
||||
histDsCols = [keyCol]
|
||||
resultData = []
|
||||
for i,row in enumerate(histDS):
|
||||
if i%interval == 0:
|
||||
timeUnitStr = system.date.format(row["t_stamp"],"yyyy-MM-dd HH:mm")
|
||||
GroupDict.setdefault(timeUnitStr, {})
|
||||
GroupDict[timeUnitStr]={"values":[]}
|
||||
# GroupDict[quarterHrStr].append(row["SumRealPwr"])
|
||||
for col in histDsCols:
|
||||
GroupDict[timeUnitStr]["values"].append(row[col] if row[col] is not None else 0)
|
||||
sortedDateKeys = sorted(GroupDict.keys(), key=lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M'))
|
||||
for dt_str in sortedDateKeys:
|
||||
intervalAvg = sum(GroupDict[dt_str]["values"])/len(GroupDict[dt_str]["values"])
|
||||
intervalPeak = max(GroupDict[dt_str]["values"])
|
||||
oneRow = [system.date.parse(dt_str, "yyyy-MM-dd HH:mm"), intervalPeak, intervalAvg]
|
||||
resultData.append(oneRow)
|
||||
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(["t_stamp"]+colNames, resultData)), GroupDict
|
||||
|
||||
def calcPUE(totalLoadDict, critLoadDict):
|
||||
resultData = []
|
||||
sortedDateKeys = sorted(totalLoadDict.keys(), key=lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M'))
|
||||
|
||||
for dt_str in sortedDateKeys:
|
||||
pue_intervalList= [a/b if b> 0 else -1 for a,b in zip(critLoadDict[dt_str]["values"], totalLoadDict[dt_str]["values"])]
|
||||
resultData.append([system.date.parse(dt_str, "yyyy-MM-dd HH:mm"), max(pue_intervalList), sum(pue_intervalList)/len(pue_intervalList)])
|
||||
|
||||
return system.dataset.toPyDataSet(system.dataset.toDataSet(["t_stamp","PeakPUE", "AvgPUE"], resultData))
|
||||
|
||||
def beginReport(startDate, endDate, intervalMin=15, location=None):
|
||||
"""
|
||||
Single call point for Report data fetch
|
||||
"""
|
||||
totalLoadHist = getRawDataTotalLoad(startDate, endDate, location)
|
||||
critLoadHist = getRawDataCriticalLoad(startDate, endDate, location)
|
||||
|
||||
# rawData = reports.MBR.common.util.mergeHistorianData(totalLoadHist, critLoadHist)
|
||||
|
||||
totalLoadHist = reports.MBR.common.util.generateSumCol(totalLoadHist, sumColName="TotalLoad")
|
||||
totalLoadHist, totalLoadDict = processCalcPeakAvg(totalLoadHist, "TotalLoad", ["PeakTotalLoad","AvgTotalLoad"], intervalMin)
|
||||
critLoadHist = reports.MBR.common.util.generateSumCol(critLoadHist, sumColName="CriticalLoad")
|
||||
critLoadHist, critLoadDict = processCalcPeakAvg(critLoadHist, "CriticalLoad", ["PeakCriticalLoad","AvgCriticalLoad"], intervalMin)
|
||||
|
||||
pueData = calcPUE(totalLoadDict, critLoadDict)
|
||||
|
||||
summaryData = reports.MBR.common.util.mergeHistorianData(totalLoadHist, critLoadHist)
|
||||
summaryData = reports.MBR.common.util.mergeHistorianData(summaryData, pueData)
|
||||
|
||||
return {"summaryData": summaryData}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "8d21970d4b74c835aecd7fc6bc0aa8409c29a8cfbc8690d65c4cbe2ae55390a5",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
def getUPSPaths(location=None):
|
||||
return reports.MBR.criticalpower.getPowerPaths(location)
|
||||
|
||||
def getPowerHist(startDate,endDate, location=None):
|
||||
tagpaths = getUPSPaths(location)
|
||||
|
||||
hist = reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={})
|
||||
return reports.MBR.common.util.generateSumCol(hist, sumColName="CalculatedUPSTotalkW")
|
||||
|
||||
|
||||
def processData(startDate,endDate, interval, location=None):
|
||||
histDS = getPowerHist(startDate,endDate, location)
|
||||
return reports.MBR.common.util.averageIntervalData(histDS, interval)
|
||||
|
||||
def beginReport(startDate,endDate, intervalMins, location=None):
|
||||
return processData(startDate,endDate, intervalMins, location)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "5284378bcacb947c8e3a096e6f2979879b0453af15013d3c670f51ca205126b2",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
from datetime import datetime
|
||||
def getTagPaths(location=None):
|
||||
"""
|
||||
Get all the relvant tagpaths needed for the historian query
|
||||
"""
|
||||
baseTagPaths = reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getGeneratorBase(), location)
|
||||
return baseTagPaths
|
||||
|
||||
def getHistoryLastValues(genBasePath, pqmBasePath, sDate, eDate):
|
||||
tagpaths = ["%s/Fuel Percent"%(genBasePath), "%s/Fuel Gallons"%(genBasePath), "%s/Real Power"%(pqmBasePath)]
|
||||
pyDS = reports.MBR.common.util.getHistory(tagpaths, sDate, eDate, customArgs={"noInterpolation":False})
|
||||
# print tagpaths, len(pyDS)
|
||||
return [c for c in pyDS[-1]]
|
||||
|
||||
def buildFinalTable(startDate,endDate,location=None):
|
||||
lastValueStartDate = system.date.addMinutes(endDate, -10)
|
||||
header = ["Generator", "FuelPercent","FuelGal","RatedLoading","CurrentLoading","RunHours"]
|
||||
allRows = []
|
||||
for genPath,pqmPath in getTagPaths(location):
|
||||
genName = genPath.split("_")[-1].replace("GEN-","")
|
||||
tstamp, fuel_perc, fuel_gal, curLoad = getHistoryLastValues(genPath, pqmPath, lastValueStartDate, endDate)
|
||||
# now get the meta values
|
||||
fullLoadKW, fullLoadGPH= [t.value for t in system.tag.readBlocking(["%s/Meta/Full Load kW"%genPath,"%s/Meta/Full Load GPH"%genPath])]
|
||||
if fullLoadGPH is None:
|
||||
fullLoadGPH = 180.0
|
||||
if fuel_gal is None:
|
||||
fuel_gal = 1500
|
||||
allRows.append([genName,fuel_perc,fuel_gal,fullLoadKW,curLoad, fuel_gal/fullLoadGPH])
|
||||
|
||||
return system.dataset.sort(system.dataset.toDataSet(header, allRows), "Generator", True)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "c493bf7d137fb805ec563f8e44ebd04f6a81a6b16f0d0690e33b17d2e03efca9",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
Logger = system.util.getLogger("reports.MBR.totalpower")
|
||||
def getTagPaths(location=None):
|
||||
"""
|
||||
Get all the relvant tagpaths needed for the historian query
|
||||
"""
|
||||
baseTagPaths = reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getPQMBase(), location)
|
||||
return baseTagPaths
|
||||
|
||||
def getRealPowerPaths(location=None):
|
||||
paths = []
|
||||
for path in reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getPQMBase(), location):
|
||||
paths.append(path+"/Real Power")
|
||||
return paths
|
||||
|
||||
def getPDURealPowerPaths(location=None):
|
||||
paths = []
|
||||
for path in reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getPDUBase(), location):
|
||||
paths.append(path+"/PQM/Real Power")
|
||||
return paths
|
||||
|
||||
def getRawDataTotalLoad(startDate, endDate, location, customArgs= {}):
|
||||
logger = Logger.createSubLogger("getRawDataTotalLoad")
|
||||
logger.debug("BEGIN")
|
||||
startDur = time.time()
|
||||
tagpaths = getRealPowerPaths(location)
|
||||
hist = reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={})
|
||||
logger.debug("completed in %s"%(time.time()-startDur))
|
||||
return hist
|
||||
|
||||
def getRawDataCriticalLoad(startDate, endDate, location, customArgs= {}):
|
||||
logger = Logger.createSubLogger("getRawDataTotalLoad")
|
||||
logger.debug("BEGIN")
|
||||
startDur = time.time()
|
||||
tagpaths = getPDURealPowerPaths(location)
|
||||
hist = reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={})
|
||||
|
||||
logger.debug("completed in %s"%(time.time()-startDur))
|
||||
return hist
|
||||
|
||||
def generateSumCol(hist):
|
||||
return reports.MBR.common.util.generateSumCol(hist, sumColName="SumRealPwr")
|
||||
# allHeaders = system.dataset.getColumnHeaders(hist)
|
||||
#
|
||||
# sumCol = []
|
||||
# for row in hist:
|
||||
# try:
|
||||
# sumCol.append(sum([row[h] for h in allHeaders[1:] if row[h] is not None]))
|
||||
# except:
|
||||
# sumCol.append(0)
|
||||
#
|
||||
# return system.dataset.toPyDataSet(system.dataset.addColumn(hist, sumCol, "SumRealPwr", float))
|
||||
|
||||
def calculateValues(data, interval=15):
|
||||
"""
|
||||
Calculate the Average, Min Average, Max Average
|
||||
Args:
|
||||
data: historian data, expected in minute intervals
|
||||
interval: grouping by minutes, None if daily
|
||||
Returns:
|
||||
dictionary containing calculated data
|
||||
"""
|
||||
data = system.dataset.toPyDataSet(data)
|
||||
res = {"MinAvg":-1,"MaxAvg":-1,"ResultData":None}
|
||||
dailyGroup= {}
|
||||
resultData = []
|
||||
allAvgs = []
|
||||
rawSum = []
|
||||
timeUnitStr = ""
|
||||
GroupDict= {} # represents {"datetime":{"col1":[], "col2":[]...}}
|
||||
histDsCols = ["SumRealPwr"]
|
||||
resultData = []
|
||||
# Construct / Group
|
||||
for i,row in enumerate(data):
|
||||
if i%interval == 0:
|
||||
timeUnitStr = system.date.format(row["t_stamp"],"yyyy-MM-dd HH:mm")
|
||||
GroupDict.setdefault(timeUnitStr, {})
|
||||
GroupDict[timeUnitStr]={col:[] for col in histDsCols}
|
||||
# GroupDict[quarterHrStr].append(row["SumRealPwr"])
|
||||
for col in histDsCols:
|
||||
colVal = row[col] if row[col] is not None else 0
|
||||
rawSum.append(colVal)
|
||||
GroupDict[timeUnitStr][col].append(row[col] if row[col] is not None else 0)
|
||||
sortedDateKeys = sorted(GroupDict.keys(), key=lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M'))
|
||||
# Create
|
||||
for dt_str in sortedDateKeys:
|
||||
colAvgs= [sum(GroupDict[dt_str][col])/len(GroupDict[dt_str][col]) for col in histDsCols]
|
||||
allAvgs.append(colAvgs[0])
|
||||
oneRow = [system.date.parse(dt_str, "yyyy-MM-dd HH:mm")]+ colAvgs
|
||||
resultData.append(oneRow)
|
||||
|
||||
res["MinAvg"] = min(allAvgs) if len(allAvgs)> 0 else 0
|
||||
res["MaxAvg"] = max(allAvgs) if len(allAvgs)> 0 else 0
|
||||
res["RawMin"] = min(rawSum) if len(rawSum)> 0 else 0
|
||||
res["RawMax"] = max(rawSum) if len(rawSum)> 0 else 0
|
||||
|
||||
res["ResultData"] = resultData
|
||||
res["ResultDS"] = system.dataset.toDataSet(["Date", "Average"], resultData)
|
||||
res["RawDS"] = data
|
||||
return res
|
||||
|
||||
def generatePUE(mergedData):
|
||||
"""
|
||||
Calculate the PUE based on the time column.
|
||||
Since there's only 3 columns, date/time, Average, AverageCritical
|
||||
"""
|
||||
headers = system.dataset.getColumnHeaders(mergedData)
|
||||
axisHeader = headers[0]
|
||||
newHeaders= [axisHeader, "PUE"]
|
||||
allRows= []
|
||||
singleVals = []
|
||||
for row in system.dataset.toPyDataSet(mergedData):
|
||||
try:
|
||||
rowPUE = row["Average"]/row["AverageCritical"]
|
||||
except:
|
||||
rowPUE = -1
|
||||
|
||||
allRows.append([row[axisHeader], rowPUE])
|
||||
singleVals.append(rowPUE)
|
||||
|
||||
return {"ResultDS":system.dataset.toDataSet(newHeaders, allRows),
|
||||
"Min":min(singleVals),
|
||||
"Max":max(singleVals)}
|
||||
|
||||
def getInputData(startDate, endDate, location=None, calcInterval=60):
|
||||
"""
|
||||
|
||||
"""
|
||||
final = {"TotalLoad":{}, "CriticalLoad":{}, "MergedDS":{}}
|
||||
rawDataTotal = generateSumCol(getRawDataTotalLoad(startDate, endDate, location))
|
||||
totalLoadDict = calculateValues(rawDataTotal, calcInterval)
|
||||
final["TotalLoad"] = totalLoadDict
|
||||
|
||||
rawDataCritical = generateSumCol(getRawDataCriticalLoad(startDate, endDate, location))
|
||||
totalCriticalDict = calculateValues(rawDataCritical, calcInterval)
|
||||
final["CriticalLoad"] = totalCriticalDict
|
||||
|
||||
return final
|
||||
|
||||
def MergeTotalCritical(ds1,ds2):
|
||||
# extract the rows of average from ds2
|
||||
newCol =[]
|
||||
for row in system.dataset.toPyDataSet(ds2):
|
||||
newCol.append(row["Average"])
|
||||
|
||||
return system.dataset.addColumn(ds1, newCol, "AverageCritical", float)
|
||||
def DoAll(startDate, endDate, location=None, interval= 60):
|
||||
"""
|
||||
Single call point for Report data fetch
|
||||
"""
|
||||
final = getInputData(startDate,endDate,location, interval)
|
||||
|
||||
try:
|
||||
final["MergedDS"] = MergeTotalCritical(final["TotalLoad"]["ResultDS"],final["CriticalLoad"]["ResultDS"])
|
||||
final["PUE"] = generatePUE(final["MergedDS"])
|
||||
except:
|
||||
final["MergedDS"] = final["TotalLoad"]["ResultDS"]
|
||||
|
||||
return final
|
||||
|
||||
def doAllInterval(startDate, endDate, location=None, intervalMins=15):
|
||||
final = getInputData(startDate,endDate,location, intervalMins)
|
||||
try:
|
||||
final["MergedDS"] = MergeTotalCritical(final["TotalLoad"]["ResultDS"],final["CriticalLoad"]["ResultDS"])
|
||||
final["PUE"] = generatePUE(final["MergedDS"])
|
||||
except:
|
||||
final["MergedDS"] = final["TotalLoad"]["ResultDS"]
|
||||
|
||||
return final
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "fdae4ca1bbcf42fc23dd4f0e8a3b613fd7df40fede069da20d90bfaa1dfa3d70",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
from datetime import datetime
|
||||
|
||||
def getTagPaths(location=None):
|
||||
"""
|
||||
Get all the relvant tagpaths needed for the historian query
|
||||
"""
|
||||
baseTagPaths = reports.MBR.common.util.getBaseTagPaths(reports.MBR.common.static.getUtilitySES(), location)
|
||||
return baseTagPaths
|
||||
|
||||
def getEnergyHistory(startDate, endDate, location=None):
|
||||
tagpaths = ["%s/Real Energy"%(path) for path in getTagPaths(location)]
|
||||
return reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={})
|
||||
|
||||
def getRealPowerHistory(startDate,endDate, location=None):
|
||||
tagpaths = ["%s/Real Power"%(path) for path in getTagPaths(location)]
|
||||
return reports.MBR.common.util.getHistory(tagpaths, startDate, endDate, customArgs={"intervalMinutes":60})
|
||||
|
||||
|
||||
def generateEnergyData(startDate, endDate, location=None):
|
||||
"""
|
||||
We'll take the actual start and end dates, but because we only care about the first entry and last entry we'll do
|
||||
2 queries to get the head and tail of the range
|
||||
"""
|
||||
qStart = startDate
|
||||
qEnd = system.date.addMinutes(startDate, 10)
|
||||
hist = getEnergyHistory(qStart, qEnd, location)
|
||||
energyStart, energyEnd = (), ()
|
||||
if len(hist)>0:
|
||||
energyStart= (hist[0][1], hist[0][2])
|
||||
|
||||
qStart = system.date.addMinutes(endDate, -10)
|
||||
qEnd = endDate
|
||||
hist = getEnergyHistory(qStart, qEnd, location)
|
||||
if len(hist)>0:
|
||||
energyEnd= (hist[-1][1], hist[-1][2])
|
||||
return energyStart, energyEnd
|
||||
|
||||
def generateRealPowerTrend(startDate,endDate, location=None):
|
||||
hist = getRealPowerHistory(startDate, endDate, location)
|
||||
return reports.MBR.common.util.generateSumCol(hist, "SumRealPwr")
|
||||
|
||||
def getRawData(startDate,endDate, intervalMins = 15, location=None):
|
||||
# Try doing this the direct way through historian
|
||||
tagpaths = ["%s/Real Power"%(path) for path in getTagPaths(location)] + ["%s/Real Energy"%(path) for path in getTagPaths(location)]
|
||||
return system.dataset.toPyDataSet(
|
||||
system.tag.queryTagHistory(tagpaths, startDate, endDate, intervalMinutes= intervalMins,
|
||||
columnNames= ["SES-1A Pwr","SES-1B Pwr", "SES-1A Energy", "SES-1B Energy"],
|
||||
aggregationModes=["SimpleAverage","SimpleAverage","LastValue","LastValue"], noInterpolation=True,
|
||||
ignoreBadQuality=True)
|
||||
)
|
||||
|
||||
def doReport(startDate, endDate, intervalMin= 15):
|
||||
"""
|
||||
This report runs only for the SES1A and SES1B yard.
|
||||
"""
|
||||
res= {"RealPower":None, "RealEnergy":{}}
|
||||
sEnergy, eEnergy= generateEnergyData(startDate,endDate, "Yard")
|
||||
res["RealEnergy"]["1A"] = {"Start":sEnergy[0], "End":eEnergy[0]}
|
||||
res["RealEnergy"]["1B"] = {"Start":sEnergy[1], "End":eEnergy[1]}
|
||||
|
||||
powerChart = generateRealPowerTrend(startDate, endDate, "Yard")
|
||||
oldHeader = system.dataset.getColumnHeaders(powerChart)
|
||||
newHeader = ["t_stamp", "SES_1A", "SES_1B", "SumPower"]
|
||||
allRows = [[row[c] for c in oldHeader] for row in powerChart]
|
||||
|
||||
res["RealPower"] = system.dataset.toDataSet(newHeader, allRows)
|
||||
res["RawData"] = reports.MBR.utility.getRawData(startDate,endDate, intervalMin, "Yard")
|
||||
return res
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "208b7e2940b0b77aa9dcbe800f07893cffdbdc3e4a2261c48bcfdde60067c876",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:15Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
from org.jfree.chart.plot import ValueMarker,XYPlot
|
||||
from org.jfree.ui import RectangleAnchor,TextAnchor
|
||||
from java.awt import Color, BasicStroke
|
||||
|
||||
|
||||
def newMarker(y, labelText, colorStr):
|
||||
marker = ValueMarker(y)
|
||||
marker.setPaint(Color.decode(colorStr))
|
||||
marker.setLabel(labelText)
|
||||
marker.setLabelTextAnchor(TextAnchor.CENTER_LEFT)
|
||||
marker.setStroke(BasicStroke(2.5))
|
||||
|
||||
return marker
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "d717900627819765dd4441ec9c9e14e531feef7f050dcdd5a57f0bdb55319c6d",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
def showAlert(state, title, message, showCloseBtn, btnTextPrimary, btnTextSecondary, btnIconPrimary, btnIconSecondary, btnIconAlignment, btnActionPrimary, btnActionSecondary, btnActionClose):
|
||||
params = {"state":state, "title":title, "message":message, "showCloseBtn":showCloseBtn, "btnTextPrimary":btnTextPrimary, "btnTextSecondary":btnTextSecondary, "btnIconPrimary":btnIconPrimary, "btnIconSecondary":btnIconSecondary, "btnIconAlignment":btnIconAlignment, "btnActionPrimary":btnActionPrimary, "btnActionSecondary":btnActionSecondary, "btnActionClose":btnActionClose}
|
||||
system.perspective.openPopup(id="alertDialog", view="Library/Alerts/alert", params=params, showCloseIcon=False, draggable=False, resizable=False, modal=True, overlayDismiss=True, btnActionPrimary="closePopup")
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"lastModificationSignature": "934536a36388b7109a117418e46c7687f987981a0a52aadbd3e88d84f7c27cfc",
|
||||
"hintScope": 2,
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
GWMap= {"172.17.19.32":"HMI1",
|
||||
"172.17.19.33":"HMI1-Backup",
|
||||
"172.17.19.41":"IO1",
|
||||
"172.17.19.42":"IO2",
|
||||
"172.17.19.43":"IO3",
|
||||
"172.17.19.44":"IO4",
|
||||
"172.17.19.45":"IO5",
|
||||
"172.17.19.46":"IO6",
|
||||
"172.17.19.49":"IO1-Backup",
|
||||
}
|
||||
|
||||
def getGatewayStatus(ipAddr):
|
||||
result = system.net.httpGet("http://"+ipAddr + ":8088/main/system/gwinfo")
|
||||
return {k: v for k, v in [item.split("=") for item in [pair for pair in result.split(";") if pair]]}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "4cf3610be781555846730c4ff4457eea433428c471e1000fb88799f1ab89e1ac",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
def browseTags(path="", filters=None, expanded=False):
|
||||
treeItems = []
|
||||
results = system.tag.browse(path, {} if filters == None else filters)
|
||||
|
||||
if results != None and results.getResults() != None:
|
||||
for result in results.getResults():
|
||||
name = result["name"]
|
||||
if name != "_types_":
|
||||
treeItem = {"label":name, "expanded":expanded, "data":{"path":result["fullPath"], "hasChildren":False}}
|
||||
treeItem["items"] = []
|
||||
|
||||
if result["hasChildren"]:
|
||||
treeItem["items"].append({"label":"Click to load...", "expanded":expanded, "data":{"path":result["fullPath"], "hasChildren":True}, "items":[], "icon":{"path":"material/hourglass_empty"}})
|
||||
|
||||
treeItems.append(treeItem)
|
||||
|
||||
return treeItems
|
||||
|
||||
def browseHistoryTags(path="", filters=None, expanded=False):
|
||||
treeItems = []
|
||||
results = system.tag.browseHistoricalTags(path, [] if filters == None else filters)
|
||||
|
||||
if results != None and results.getResults() != None:
|
||||
for result in results.getResults():
|
||||
name = result.getPath().toString().replace(path, "").replace(":/", "").replace("/", "")
|
||||
treeItem = {"label":name, "expanded":expanded, "data":{"path":result.getPath().toString(), "hasChildren":False}}
|
||||
treeItem["items"] = []
|
||||
|
||||
if result.hasChildren():
|
||||
treeItem["items"].append({"label":"Click to load...", "expanded":expanded, "data":{"path":result.getPath().toString(), "hasChildren":True}, "items":[], "icon":{"path":"material/hourglass_empty"}})
|
||||
|
||||
treeItems.append(treeItem)
|
||||
|
||||
return treeItems
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"lastModificationSignature": "4692305066947531565acebf6c0d58f6ae10ecc094549a3411c3b3af742824c2",
|
||||
"hintScope": 2,
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
defaultProvider = "[MQTT]" # Target Provider Name
|
||||
defaultSourceProvider = "[Ignition_Common_IO_Gtwy]" # "[Ignition_Common_IO_Gtwy]"
|
||||
Logger = system.util.getLogger("utils.admin.tags.helper")
|
||||
def generateUDTDef():
|
||||
""" One stop shop execution to deploy the MQTT tags.
|
||||
|
||||
"""
|
||||
logger = Logger.createSubLogger("generateUDTDef")
|
||||
|
||||
utils.admin.tags.utils.definition.transformBase()
|
||||
utils.admin.tags.utils.definition.transformObjects(udtFolder= "Objects/Parent")
|
||||
utils.admin.tags.utils.definition.transformObjects(udtFolder= "Objects")
|
||||
|
||||
def generateUDTInstances():
|
||||
""" Generate UDT Instances based off of MQTT Reference UDTs. Iterate over every provider and perform the execution
|
||||
"""
|
||||
logger = Logger.createSubLogger("generateUDTInstances")
|
||||
for prov in ["Ignition_Common_IO_Gtwy", "Ignition_SATB1_IO1_default", "Ignition_SATB1_IO2_default",
|
||||
"Ignition_SATB1_IO3_default", "Ignition_SATB1_IO4_default", "Ignition_SATB1_IO5_default"]:
|
||||
logger.debug("Processing provider: %s"%(prov))
|
||||
utils.admin.tags.utils.instances.transformObjects(prov)
|
||||
logger.debug("Finished Processing")
|
||||
|
||||
def generateUDTInstances_demo():
|
||||
""" Generate UDT Instances based off of MQTT Reference UDTs. Iterate over every provider and perform the execution
|
||||
"""
|
||||
# utils.admin.tags.utils.instances.transformObjects("[Ignition_Common_IO_Gtwy]Yard/SATB1_YARD_SES_1A")
|
||||
utils.admin.tags.utils.instances.transformObjects("[Ignition_Common_IO_Gtwy]Yard")
|
||||
|
||||
def exportAllTags():
|
||||
""" Browse all the tags in the MQTT provider and generate a tag export.
|
||||
|
||||
"""
|
||||
return system.dataset.toExcel(True, [utils.admin.tags.utils.instances.generateTagExport()], ["All Tags"])
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "292eebcb9c5ee8c670e34443dcad544a413f13afb6cbda9928fb7f476a660e35",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import pprint
|
||||
from com.inductiveautomation.ignition.common.tags.config.types import TagObjectType
|
||||
defaultProvider= utils.admin.tags.helper.defaultProvider
|
||||
defaultSrcProv = utils.admin.tags.helper.defaultSourceProvider
|
||||
|
||||
def getUDTBaseParameters():
|
||||
return {'srcPath': {"dataType":"String", "value":None},
|
||||
'tagGroup': {"dataType":"String", "value":"Default"}}
|
||||
|
||||
def transformBase():
|
||||
""" Read the [default]Base UDT and convert it into the Reference Tag equivalent
|
||||
New logic to create an additional expression tag bound to the srcPath parameter
|
||||
Returns: Nothing, will create the UDT in the global defaultProvider
|
||||
"""
|
||||
newTagConfig= (transformSingleObject("%s_types_/Base"%(defaultSrcProv)))
|
||||
devPathTag={'dataType': "String",
|
||||
'expression':'{srcPath}',
|
||||
'name': 'DeviceTagPath',
|
||||
'tagType': "AtomicTag",
|
||||
'valueSource': 'expr'}
|
||||
newTagConfig["parameters"] = getUDTBaseParameters()
|
||||
newTagConfig["tags"][0]["tags"].append(devPathTag)
|
||||
|
||||
|
||||
pprint.pprint(newTagConfig)
|
||||
system.tag.configure("%s/_types_"%(defaultProvider), [newTagConfig], "o")
|
||||
|
||||
def transformObjects(udtFolder= "Objects"):
|
||||
""" Scan for all of the Object UDTs in [default] and create the Reference tag version
|
||||
Returns: Nothing
|
||||
"""
|
||||
metaTagNames = [t["name"] for t in system.tag.browse("[MQTT]_types_/Base/Meta")]
|
||||
def normalizeTagConfig(tagConfig):
|
||||
# tagConfig.pop("path")
|
||||
tagConfig.pop("parameters")
|
||||
# Clean up the meta tags inherited from base
|
||||
for t in tagConfig["tags"]:
|
||||
if t["name"] == "Meta":
|
||||
t["tags"] = [item for item in t["tags"] if item['name'] not in metaTagNames]
|
||||
|
||||
return tagConfig
|
||||
allTagConfig = {}
|
||||
for udtPath in recBrowse("%s_types_/%s"%(defaultSrcProv, udtFolder)):
|
||||
newTagConfig = normalizeTagConfig(transformSingleObject(udtPath))
|
||||
if "parameters" not in newTagConfig.keys():
|
||||
newTagConfig["parameters"] = getUDTBaseParameters()
|
||||
rootFolder = "/".join(udtPath.split("/")[:-1]).replace(defaultSrcProv,defaultProvider)
|
||||
allTagConfig.setdefault(rootFolder, [])
|
||||
|
||||
allTagConfig[rootFolder].append(newTagConfig)
|
||||
|
||||
for k,v in allTagConfig.iteritems():
|
||||
system.tag.configure(k, v, "o")
|
||||
|
||||
|
||||
def recBrowse(path):
|
||||
""" Recursively process the folder
|
||||
"""
|
||||
listOfPaths = []
|
||||
for tag in system.tag.browse(path):
|
||||
if tag["tagType"] == TagObjectType.Folder:
|
||||
if tag["name"] != "Parent": # don't browse the parent folder when we're doing the root Objects folder
|
||||
listOfPaths.extend(recBrowse(str(tag["fullPath"])))
|
||||
elif tag["tagType"] == TagObjectType.UdtType:
|
||||
listOfPaths.append(str(tag["fullPath"]))
|
||||
return listOfPaths
|
||||
|
||||
|
||||
def transformSingleObject(udtPath):
|
||||
""" Convert a single UDT Object to a reference tag equivalent. Reusable in individual scenarios
|
||||
Returns: JSON configuration for the new tag object
|
||||
"""
|
||||
curTagConfig = system.tag.getConfiguration(udtPath, True)[0]
|
||||
# curTagConfig.pop("path") # don't need this since we're creating new
|
||||
|
||||
curTagConfig["tags"] = recFolderTransform(curTagConfig["tags"], curTagConfig["name"])
|
||||
return curTagConfig
|
||||
|
||||
|
||||
def recFolderTransform(tagObj, rootObjName= "", folderPath=""):
|
||||
""" Recursively process the folder's contents to convert the existing tags to Reference Tags
|
||||
Returns: Newly constructed JSON
|
||||
"""
|
||||
alarmSummIdx = -1
|
||||
for idx, obj in enumerate(tagObj):
|
||||
# print obj["name"], obj["tagType"]
|
||||
if obj["tagType"]== TagObjectType.Folder:
|
||||
# print " %s -- folder found"%(obj["name"])
|
||||
if "tags" in obj.keys():
|
||||
recFolderTransform(obj["tags"], folderPath="/".join([folderPath, obj["name"]]))
|
||||
elif str(obj["tagType"]) not in ["UdtInstance"] and obj["name"] != "AlarmSummary":
|
||||
obj["valueSource"] = "reference"
|
||||
# obj["sourceTagPath"] = {"bindType":"parameter", "binding":"{srcPath}/%s{TagName}"%(folderPath[:]+"/" if folderPath != "" else "")}
|
||||
obj["sourceTagPath"] = {"bindType":"parameter", "binding":"{srcPath}/%s%s"%(folderPath[1:]+"/" if folderPath != "" else "", obj["name"])} # hardcode tagpath, but change name to underscore
|
||||
obj["tagGroup"] = {"bindType":"parameter", "binding":"{tagGroup}"}
|
||||
obj["name"] = obj["name"].replace(" ", "_")
|
||||
# remove keys that are no longer relative to the MQTT transmission
|
||||
for k in ["value", "opcServer", "opcItemPath", "sampleMode", "alarms", "sampleMode", "scaleMode", "scaledHigh"]:
|
||||
obj.pop(k, None)
|
||||
# Remove historian settings since we don't need to log this
|
||||
obj["historyEnabled"]= False
|
||||
for setting in ["historyMaxAge", "historyProvider", "historyTagGroup"]:
|
||||
obj.pop(setting, None)
|
||||
elif obj["tagType"] == TagObjectType.UdtInstance:
|
||||
print "1"
|
||||
# Special handler for the Base Object
|
||||
if str(obj["name"]) == "AlarmSummary" and rootObjName=="Base" :
|
||||
replacementTag = obj["tags"][0]
|
||||
replacementTag["valueSource"] = "reference"
|
||||
replacementTag["sourceTagPath"] = {"bindType":"parameter", "binding":"{srcPath}/{TagName}/ActiveAlarms"}
|
||||
replacementTag["tagGroup"] = {"bindType":"parameter", "binding":"{tagGroup}"}
|
||||
replacementTag.pop("name")
|
||||
replacementTag.pop("path")
|
||||
replacementTag.pop("value")
|
||||
obj.pop("tags")
|
||||
obj.pop("typeId")
|
||||
|
||||
obj.update(replacementTag)
|
||||
|
||||
return tagObj
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "6a663abb11d70e8dc81771dba1a8376b078ff6b9214883b24aaebf4094561cf6",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import pprint, time
|
||||
from com.inductiveautomation.ignition.common.tags.config.types import TagObjectType
|
||||
defaultTgtProvider= utils.admin.tags.helper.defaultProvider
|
||||
defaultSrcProv = utils.admin.tags.helper.defaultSourceProvider
|
||||
thisTenant = "Valley"
|
||||
|
||||
Logger = system.util.getLogger("utils.admin.tags.utils.instances")
|
||||
|
||||
|
||||
def transformObjects(startPath):
|
||||
logger = Logger.createSubLogger("transformObjects")
|
||||
newTags = []
|
||||
for udtInst in recBrowse(startPath):
|
||||
newTags.append(createSingleInstance(udtInst))
|
||||
|
||||
system.tag.configure(defaultTgtProvider, newTags, "o")
|
||||
|
||||
def recBrowse(path):
|
||||
""" Recursively process the folder
|
||||
"""
|
||||
logger = Logger.createSubLogger("recBrowse")
|
||||
_tStartTime = time.time()
|
||||
|
||||
|
||||
listOfPaths = []
|
||||
for tag in system.tag.browse(path):
|
||||
if tag["tagType"] == TagObjectType.Folder:
|
||||
if tag["name"] not in ["Parent", "AlarmSummary"]: # don't browse the parent folder when we're doing the root Objects folder
|
||||
listOfPaths.extend(recBrowse(str(tag["fullPath"])))
|
||||
elif tag["tagType"] == TagObjectType.UdtInstance:
|
||||
listOfPaths.append(str(tag["fullPath"]))
|
||||
logger.trace("recursive browse completed in : %s"%(time.time()-_tStartTime))
|
||||
|
||||
return listOfPaths
|
||||
|
||||
|
||||
def createSingleInstance(udtPath):
|
||||
""" create a reference tag equiavalent of a UDT found
|
||||
udtPath: str the udt instance path
|
||||
|
||||
"""
|
||||
logger = Logger.createSubLogger("createSingleInstance")
|
||||
|
||||
tagConfig = system.tag.getConfiguration(udtPath, False)[0]
|
||||
|
||||
path = tagConfig["path"]
|
||||
udtType = tagConfig["typeId"]
|
||||
refPath = str(tagConfig["path"])
|
||||
pathSegments = refPath.split("/")
|
||||
prov = refPath[refPath.find("[")+1 : refPath.find("]")]
|
||||
tagName = tagConfig["name"]
|
||||
if len(tagConfig["name"].split("_")) < 3:
|
||||
# tagName = "%s_%s_%s"%(prov.replace("_","-"), pathSegments[-2],tagName)
|
||||
tagName = "%s_%s"%(pathSegments[-2],tagName)
|
||||
else:
|
||||
tagName = "%s"%(tagName)
|
||||
|
||||
newTag= {"name":tagName,
|
||||
"typeId":tagConfig["typeId"], "tagType":"UdtInstance",
|
||||
"parameters":{"srcPath":{"dataType":"String", "value":refPath}}
|
||||
}
|
||||
|
||||
return newTag
|
||||
|
||||
|
||||
def generateTagExport():
|
||||
""" Generate a dataset representing all of the discovered tags in the MQTT provider
|
||||
|
||||
"""
|
||||
def transformRow(path):
|
||||
metaProps = getMetaProp(path, ["EqName"])
|
||||
def buildRow(udtDefPath, path, tagdict, srcPath=""):
|
||||
instNameArr = path.split("/")[0].split("_")
|
||||
hasProvInPath= True if "Ignition_" in path else False
|
||||
if hasProvInPath:
|
||||
loc = instNameArr[5]
|
||||
devName = "_".join(instNameArr[4:])
|
||||
else:
|
||||
loc = instNameArr[1]
|
||||
devName = "_".join(instNameArr[2:])
|
||||
|
||||
# return [udtDefPath[-2], udtDefPath[-1], "%s/%s"%(path, tagdict["name"]), tagdict.get("engUnit",""), devName, loc, tagdict["name"].replace("_"," "),
|
||||
# tagdict["name"].replace(" ","_"), path]
|
||||
localPath = "%s/%s"%(path, tagdict["name"])
|
||||
remotePath, tagTenant = getCustomTenantProp(localPath)
|
||||
return [udtDefPath[-2], udtDefPath[-1], remotePath, tagdict.get("engUnit",""),
|
||||
devName if metaProps.get("EqName","") == "" else metaProps["EqName"], # DeviceName
|
||||
loc,
|
||||
tagTenant if tagTenant is not None else tagdict["name"].replace("_"," "), # Description
|
||||
tagdict["name"].replace(" ","_"),
|
||||
remotePath] # Notes
|
||||
|
||||
rows = []
|
||||
config = system.tag.getConfiguration(path, True)[0]
|
||||
udtDefPath = str(config.get("typeId","/")).split("/")
|
||||
srcPath = config["parameters"].get("srcPath").value
|
||||
for tag in config["tags"]:
|
||||
if tag["tagType"] == TagObjectType.Folder and tag["name"] not in ["Meta"]:
|
||||
for fTag in tag["tags"]:
|
||||
# rows.append(buildRow(udtDefPath, "%s/%s"%(path, tag["name"]), fTag))
|
||||
rows.append(buildRow(udtDefPath, "%s/%s"%(path, tag["name"]), fTag, "%s/%s"%(srcPath, tag["name"])))
|
||||
else:
|
||||
if tag["name"] not in ["Meta"]:
|
||||
rows.append(buildRow(udtDefPath, path, tag, srcPath))
|
||||
|
||||
return rows
|
||||
_udtInstList= utils.admin.tags.utils.instances.recBrowse(defaultTgtProvider)
|
||||
header = ["componentType", "topic", "tag", "units", "deviceName", "hallId", "description", "dataPointType", "Notes"]
|
||||
allRows = []
|
||||
for udtInstPath in _udtInstList:
|
||||
allRows.extend(transformRow(udtInstPath))
|
||||
|
||||
fullDS = system.dataset.toDataSet(header, allRows)
|
||||
return fullDS
|
||||
# return system.dataset.toExcel(True, [fullDS], ["All Points"])
|
||||
|
||||
def getMetaProp(instPath, propNames):
|
||||
""" Get the meta properties of a tag
|
||||
returns: dictionary of the propertyname and value
|
||||
"""
|
||||
pathsToRead = ["%s/Meta/%s"%(instPath,p) for p in propNames]
|
||||
qvs = system.tag.readBlocking(pathsToRead)
|
||||
return {p:qv.value for p,qv in zip(propNames, qvs)}
|
||||
|
||||
def getCustomTenantProp(localTagPath):
|
||||
print localTagPath
|
||||
tagpath = system.tag.readBlocking("%s.%s"%(localTagPath,"SourceTagPath"))[0].value
|
||||
return tagpath, system.tag.readBlocking("%s.%s"%(tagpath,thisTenant))[0].value
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "5f3ad9988a1dca97daeea68b398815293009d7ae28f733c15c6e4f08defc5a80",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
db = "Ignition"
|
||||
def saveAllSettings(homepage, theme, username):
|
||||
settingDict = {"Homepage":homepage, "Theme":theme}
|
||||
saveSettings(settingDict, username)
|
||||
|
||||
def saveSettings(settingDict, username):
|
||||
""" save all settings from a dictionary
|
||||
settingDict: pyDictionary ex {"Homepage":homepage, "Theme":theme}, key represents settingname and value is the settingvalue
|
||||
|
||||
"""
|
||||
insQuery = "Insert into user_settings (settingname, settingvalue, username) values"
|
||||
insVals = []
|
||||
for k,v in settingDict.iteritems():
|
||||
# perform upsert
|
||||
if system.db.runPrepUpdate("Update user_settings set settingvalue= ? where settingname=? and username = ?", [ v, k, username], db) == 0:
|
||||
insQuery += "(?,?,?),"
|
||||
insVals.extend([k, v, username])
|
||||
|
||||
if len(insVals) > 0:
|
||||
system.db.runPrepUpdate(insQuery[:-1], insVals, db)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "a5594821e12b5ae5e26bec702c2c0f7936d1beb95ace9c90b248eea3eb942afa",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
from com.inductiveautomation.ignition.common import BasicDataset
|
||||
from com.inductiveautomation.ignition.common.script.builtin.DatasetUtilities import PyDataSet
|
||||
from java.util import Date
|
||||
def sanitize_tree(element):
|
||||
if hasattr(element, '__iter__'):
|
||||
if hasattr(element, 'keys'):
|
||||
return dict((k, sanitize_tree(element[k])) for k in element.keys())
|
||||
else:
|
||||
return list(sanitize_tree(x) for x in element)
|
||||
return element
|
||||
def getColumnStruct():
|
||||
return {
|
||||
"field": "",
|
||||
"visible": True,
|
||||
"editable": False,
|
||||
"render": "auto",
|
||||
"justify": "auto",
|
||||
"align": "center",
|
||||
"resizable": True,
|
||||
"sortable": True,
|
||||
"sort": "none",
|
||||
"filter": {
|
||||
"enabled": False,
|
||||
"visible": "on-hover",
|
||||
"string": {
|
||||
"condition": "",
|
||||
"value": ""
|
||||
},
|
||||
"number": {
|
||||
"condition": "",
|
||||
"value": ""
|
||||
},
|
||||
"boolean": {
|
||||
"condition": ""
|
||||
},
|
||||
"date": {
|
||||
"condition": "",
|
||||
"value": ""
|
||||
}
|
||||
},
|
||||
"viewPath": "",
|
||||
"viewParams": {},
|
||||
"boolean": "checkbox",
|
||||
"number": "value",
|
||||
"progressBar": {
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"bar": {
|
||||
"color": "",
|
||||
"style": {
|
||||
"classes": ""
|
||||
}
|
||||
},
|
||||
"track": {
|
||||
"color": "",
|
||||
"style": {
|
||||
"classes": ""
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"enabled": True,
|
||||
"format": "0,0.##",
|
||||
"justify": "center",
|
||||
"style": {
|
||||
"classes": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"toggleSwitch": {
|
||||
"color": {
|
||||
"selected": "",
|
||||
"unselected": ""
|
||||
}
|
||||
},
|
||||
"nullFormat": {
|
||||
"includeNullStrings": False,
|
||||
"strict": False,
|
||||
"nullFormatValue": ""
|
||||
},
|
||||
"numberFormat": "0,0.##",
|
||||
"dateFormat": "MMM-DD HH:mm",
|
||||
"width": "",
|
||||
"strictWidth": False,
|
||||
"header": {
|
||||
"title": "",
|
||||
"justify": "left",
|
||||
"align": "center",
|
||||
"style": {
|
||||
"classes": ""
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"title": "",
|
||||
"justify": "left",
|
||||
"align": "center",
|
||||
"style": {
|
||||
"classes": ""
|
||||
}
|
||||
},
|
||||
"style": {
|
||||
"classes": ""
|
||||
}
|
||||
}
|
||||
def xformTableColumns(dataIn):
|
||||
|
||||
if isinstance(dataIn, PyDataSet):
|
||||
data = dataIn
|
||||
elif isinstance(dataIn, BasicDataset):
|
||||
data = system.dataset.toPyDataSet(dataIn)
|
||||
elif isinstance(dataIn, dict):
|
||||
pass
|
||||
|
||||
colHeaders = system.dataset.getColumnHeaders(data)
|
||||
columns = []
|
||||
if len(data)> 0:
|
||||
for col in colHeaders:
|
||||
struct= utils.helper.getColumnStruct()
|
||||
struct["field"] = col
|
||||
if isinstance(data[0][col], Date):
|
||||
struct["render"] = "date"
|
||||
columns.append(struct)
|
||||
else:
|
||||
for col in colHeaders:
|
||||
struct= utils.helper.getColumnStruct()
|
||||
struct["field"] = col
|
||||
columns.append(struct)
|
||||
|
||||
return columns
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "5ffc764eb84cbc2ad5c24750ebca9a5c2be526ee00942642d1533c9806330d12",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
def open_popup():
|
||||
system.perspective.openPopup("maintNotification", "Delete ME/xychart")
|
||||
|
||||
system.util.invokeLater(open_popup,5000) # Schedule popup to open after 5000 milliseconds (5 seconds)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scope": "A",
|
||||
"version": 1,
|
||||
"restricted": false,
|
||||
"overridable": true,
|
||||
"files": [
|
||||
"code.py"
|
||||
],
|
||||
"attributes": {
|
||||
"hintScope": 2,
|
||||
"lastModificationSignature": "b59e7239880e3343a20b0af06c68f2baf5893c70e51f5701c25b12229b943dbf",
|
||||
"lastModification": {
|
||||
"actor": "Emmanuel",
|
||||
"timestamp": "2026-04-15T20:54:16Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user