2026-04-24 FE Backup

This commit is contained in:
2026-04-24 12:37:16 +00:00
parent ace79b82f7
commit 0ba11a23b8
3705 changed files with 1240402 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
{
"scope": "A",
"version": 1,
"restricted": false,
"overridable": true,
"files": [
"data.bin"
],
"attributes": {}
}

View File

@@ -0,0 +1,2 @@
select id, settingname, settingvalue from user_settings
where username= :username

View File

@@ -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
}
]
}
}

View File

@@ -0,0 +1,9 @@
SELECT
id,
body,
last_updated,
last_updated_by
FROM
tag_notes
WHERE
tag_path = :tagPath

View File

@@ -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
}
]
}
}

View File

@@ -0,0 +1 @@
INSERT INTO tag_notes (tag_path, body, last_updated, last_updated_by) VALUES (:tagPath, :body, CURRENT_TIMESTAMP, :user)

View File

@@ -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
}
]
}
}

View File

@@ -0,0 +1 @@
UPDATE tag_notes SET body = :body, last_updated = CURRENT_TIMESTAMP, last_updated_by = :user WHERE id = :id

View File

@@ -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
}
]
}
}

View File

@@ -0,0 +1,9 @@
SELECT
id,
body,
last_updated,
last_updated_by
FROM
wiki_notes
WHERE
model_no = :modelNo

View File

@@ -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
}
]
}
}

View File

@@ -0,0 +1 @@
INSERT INTO wiki_notes (model_no, body, last_updated, last_updated_by) VALUES (:modelNo, :body, CURRENT_TIMESTAMP, :user)

View File

@@ -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
}
]
}
}

View File

@@ -0,0 +1 @@
UPDATE wiki_notes SET body = :body, last_updated = CURRENT_TIMESTAMP, last_updated_by = :user WHERE id = :id

View File

@@ -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
}
]
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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('\\','/'))

View File

@@ -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"
}
}
}

View File

@@ -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))

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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()

View File

@@ -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"
}
}
}

View File

@@ -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"

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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":[],
}

View File

@@ -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"
}
}
}

View File

@@ -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))

View File

@@ -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"
}
}
}

View File

@@ -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}

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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}

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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")

View File

@@ -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"
}
}
}

View File

@@ -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]]}

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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"])

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -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