modified the script codes, now correctly displaying the states of devices, modified the device mapping and showing all the releated devices for the conveyors and chutes. added new tags for the VFD and showign falted info and possible solutions in the docked vfd view

This commit is contained in:
Salijoghli 2025-11-06 14:51:15 +04:00
parent d7b8dec39c
commit f6d53e9518
74 changed files with 2278 additions and 1150 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,308 +0,0 @@
import os, json, sys
global_device_mapping = {}
def build_device_mapping(full_tag_path):
"""
Builds global_device_mapping for devices that:
- Belong to the same PLC (index 1)
- Are children of the clicked device (start with clicked_name + "_")
"""
global global_device_mapping
global_device_mapping.clear()
try:
# Parse PLC and clicked device
path_parts = full_tag_path.split("/")
plc_name = path_parts[1] if len(path_parts) > 1 else path_parts[0]
clicked_name = path_parts[-1] if len(path_parts) > 0 else ""
if "_VFD" in clicked_name:
idx = clicked_name.find("_VFD")
if idx != -1:
clicked_name = clicked_name[:idx]
project_name = system.util.getProjectName()
base_path = (
os.getcwd().replace("\\", "/")
+ "/data/projects/"
+ project_name
+ "/com.inductiveautomation.perspective/Views/autStand/Detailed_Views/MCM-Views"
)
if not os.path.exists(base_path):
system.perspective.print("Path not found: " + base_path)
return {}
# loop through all view folders
for view_folder in os.listdir(base_path):
json_file = os.path.join(base_path, view_folder, "view.json")
if not os.path.isfile(json_file):
continue
try:
with open(json_file, "r") as fh:
view_json = json.load(fh)
except Exception:
continue
# go one level deeper: root -> children[0] (coordinateContainer) -> its children
root_children = (view_json.get("root") or {}).get("children") or []
if not root_children:
continue
container = root_children[0]
children = container.get("children") or []
for child in children:
props = child.get("props") or {}
params = props.get("params") or {}
tag_props = params.get("tagProps")
if isinstance(tag_props, list) and len(tag_props) > 0:
tag_prop = str(tag_props[0])
parts = tag_prop.split("/")
if len(parts) > 1 and parts[1] == plc_name:
dev_name = parts[-1]
if len(parts) > 3 and parts[-2] == clicked_name:
dev_name = clicked_name + "_" + parts[-1]
system.perspective.print(dev_name)
# ONLY include devices that are children of clicked_name
else:
dev_name = parts[-1]
prefix = clicked_name + "_"
if dev_name.startswith(prefix) or (len(parts) > 3 and parts[-2] == clicked_name):
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
return global_device_mapping
except Exception as e:
whid = "unknown"
try:
whid = system.tag.readBlocking("Configuration/FC")[0].value
except:
pass
logger = system.util.getLogger("%s-build_device_mapping" % whid)
exc_type, exc_obj, tb = sys.exc_info()
logger.error("Error at line %s: %s" % (tb.tb_lineno, exc_obj))
return {}
def build_device_table(self):
"""
Converts global_device_mapping into a list of dictionaries:
Keys: Device, Status
Reads each tag value, falls back to 'Unknown' if error/null.
"""
rows = []
state_mappings = {
0: "Closed",
1: "Actuated",
2: "Communication Faulted",
3: "Conveyor Running In Maintenance Mode",
4: "Disabled",
5: "Disconnected",
6: "Stopped",
7: "Enabled Not Running",
8: "Encoder Fault",
9: "Energy Management",
10: "ESTOP Was Actuated",
11: "EStopped",
12: "EStopped Locally",
13: "Extended Faulted",
14: "Full",
15: "Gaylord Start Pressed",
16: "Jam Fault",
17: "Jammed",
18: "Loading Allowed",
19: "Loading Not Allowed",
20: "Low Air Pressure Fault Was Present",
21: "Maintenance Mode",
22: "Conveyor Stopped In Maintenance Mode",
23: "Motor Faulted",
24: "Motor Was Faulted",
25: "Normal",
26: "Off Inactive",
27: "Open",
28: "PLC Ready To Run",
29: "Package Release Pressed",
30: "Power Branch Was Faulted",
31: "Pressed",
32: "Ready To Receive",
33: "Running",
34: "Started",
35: "Stopped",
36: "System Started",
37: "Unknown",
38: "VFD Fault",
39: "Conveyor Running In Power Saving Mode",
40: "Conveyor Jogging In Maintenance Mode",
41: "VFD Reset Required",
42: "Jam Reset Push Button Pressed",
43: "Start Push Button Pressed",
44: "Stop Push Button Pressed",
45: "No Container",
46: "Ready To Be Enabled",
47: "Half Full",
48: "Enabled",
49: "Tipper Faulted"
}
try:
for dev_name, info in global_device_mapping.items():
tagPath = info.get("tagPath", "")
status_value = ""
provider = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
path = provider + tagPath + "/STATE"
if tagPath:
try:
result = system.tag.readBlocking([path])[0]
status_value = state_mappings.get(result.value, "Unknown")
except:
status_value = "Unknown"
# Append as dictionary
rows.append({
'Device': dev_name,
'Status': status_value
})
return rows
except Exception as e:
system.perspective.print("Error building device table: %s" % e)
return [] # Return empty list on error
def getAllTags(self, tagPath, section="all"):
"""
Reads all tags under a UDT instance (recursively) and returns a list of dictionaries.
Supports:
- VFD (Drive folder)
- Conveyor (skips Drive)
- Chute (root + PE/PRX/EN tags)
- Single Photoeyes (PE1/PE2)
- Single Prox Sensors (PRX1/PRX2)
- Enable buttons (EN_Color, EN_State, EN_Priority)
- Tracking Photoeyes (TPE, handles both folder- and struct-style UDTs)
"""
rows = []
try:
providerPath = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
driveFolderName = "Drive"
# === Utility: read a single atomic tag ===
def readSingleTag(path, prefix=""):
try:
result = system.tag.readBlocking([providerPath + path])[0]
value = str(result.value) if result.quality.isGood() else "Unknown"
except:
value = "Unknown"
displayName = prefix + path.split("/")[-1] if prefix else path.split("/")[-1]
rows.append({
"Name": displayName,
"OPC Path": path,
"Value": value
})
# === Utility: recursive browse ===
def browseRecursive(basePath, prefix=""):
children = system.tag.browse(providerPath + basePath).getResults()
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
fullPath = str(child.get("fullPath", ""))
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
# --- Conveyor filter (skip Drive folder) ---
if section == "conveyor" and name == driveFolderName:
continue
if tagType == "Folder":
newPrefix = prefix + name + "/" if prefix else name + "/"
browseRecursive(basePath + "/" + name, newPrefix)
elif tagType == "AtomicTag":
readSingleTag(fullPath, prefix)
# === MAIN ENTRY POINT ===
if section == "vfd":
# Browse only inside Drive folder
drivePath = tagPath + "/" + driveFolderName
browseRecursive(drivePath)
elif tagPath.upper().endswith("/EN"):
# --- Handle flat EN_ tags ---
parentPath = "/".join(tagPath.split("/")[:-1])
children = system.tag.browse(providerPath + parentPath).getResults()
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
if tagType == "AtomicTag" and name.upper().startswith("EN_"):
fullPath = str(child.get("fullPath", ""))
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
readSingleTag(fullPath)
elif tagPath.upper().endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in tagPath.upper():
# --- Single sensors ---
readSingleTag(tagPath)
else:
# --- Default path ---
browseResult = system.tag.browse(providerPath + tagPath).getResults()
if not browseResult:
# Possibly a struct-style UDT (like some TPEs)
system.perspective.print("Empty browse for {}, checking struct value...".format(tagPath))
try:
result = system.tag.readBlocking([providerPath + tagPath])[0]
value = result.value
# If we got a STRUCT, expand it into sub-rows
if isinstance(value, dict):
system.perspective.print("Detected STRUCT value, expanding {}".format(tagPath))
def flattenStruct(struct, base=""):
for k, v in struct.items():
newName = base + "/" + k if base else k
if isinstance(v, dict):
flattenStruct(v, newName)
else:
rows.append({
"Name": newName,
"OPC Path": tagPath + "/" + newName,
"Value": str(v)
})
flattenStruct(value)
else:
# Not a struct, just read it normally
readSingleTag(tagPath)
except Exception as ex:
system.perspective.print("Fallback read failed for {}: {}".format(tagPath, ex))
else:
# Normal case — browse folder/UDT structure
browseRecursive(tagPath)
return rows
except Exception as e:
system.perspective.print("Error in getAllTags: {}".format(e))
return []

View File

@ -3,7 +3,7 @@
"color": "#000000",
"deviceName": "S03_1_JR1",
"priority": "No Active Alarms",
"state": "Offline"
"state": "Actuated"
},
"params": {
"demoColor": -1,

View File

@ -71,232 +71,6 @@
"persistent": true
},
"custom.state": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/State"
},
"transforms": [
{
"expression": "coalesce({value},-1)",
"type": "expression"
},
{
"fallback": "Offline",
"inputType": "scalar",
"mappings": [
{
"input": 0,
"output": "Closed"
},
{
"input": 1,
"output": "Actuated"
},
{
"input": 2,
"output": "Communication Faulted"
},
{
"input": 3,
"output": "Conveyor Running In Maintenance Mode"
},
{
"input": 4,
"output": "Disabled"
},
{
"input": 5,
"output": "Disconnected"
},
{
"input": 6,
"output": "Stopped"
},
{
"input": 7,
"output": "Enabled Not Running"
},
{
"input": 8,
"output": "Encoder Fault"
},
{
"input": 9,
"output": "Energy Management"
},
{
"input": 10,
"output": "ESTOP Was Actuated"
},
{
"input": 11,
"output": "EStopped"
},
{
"input": 12,
"output": "EStopped Locally"
},
{
"input": 13,
"output": "Extended Faulted"
},
{
"input": 14,
"output": "Full"
},
{
"input": 15,
"output": "Gaylord Start Pressed"
},
{
"input": 16,
"output": "Jam Fault"
},
{
"input": 17,
"output": "Jammed"
},
{
"input": 18,
"output": "Loading Allowed"
},
{
"input": 19,
"output": "Loading Not Allowed"
},
{
"input": 20,
"output": "Low Air Pressure Fault Was Present"
},
{
"input": 21,
"output": "Maintenance Mode"
},
{
"input": 22,
"output": "Conveyor Stopped In Maintenance Mode"
},
{
"input": 23,
"output": "Motor Faulted"
},
{
"input": 24,
"output": "Motor Was Faulted"
},
{
"input": 25,
"output": "Normal"
},
{
"input": 26,
"output": "Off Inactive"
},
{
"input": 27,
"output": "Open"
},
{
"input": 28,
"output": "PLC Ready To Run"
},
{
"input": 29,
"output": "Package Release Pressed"
},
{
"input": 30,
"output": "Power Branch Was Faulted"
},
{
"input": 31,
"output": "Pressed"
},
{
"input": 32,
"output": "Ready To Receive"
},
{
"input": 33,
"output": "Running"
},
{
"input": 34,
"output": "Started"
},
{
"input": 35,
"output": "Stopped"
},
{
"input": 36,
"output": "System Started"
},
{
"input": 37,
"output": "Unknown"
},
{
"input": 38,
"output": "VFD Fault"
},
{
"input": 39,
"output": "Conveyor Running In Power Saving Mode"
},
{
"input": 40,
"output": "Conveyor Jogging In Maintenance Mode"
},
{
"input": 41,
"output": "VFD Reset Required"
},
{
"input": 42,
"output": "Jam Reset Push Button Pressed"
},
{
"input": 43,
"output": "Start Push Button Pressed"
},
{
"input": 44,
"output": "Stop Push Button Pressed"
},
{
"input": 45,
"output": "No Container"
},
{
"input": 46,
"output": "Ready To Be Enabled"
},
{
"input": 47,
"output": "Half Full"
},
{
"input": 48,
"output": "Enabled"
},
{
"input": 49,
"output": "Tipper Faulted"
}
],
"outputType": "scalar",
"type": "map"
}
],
"type": "tag"
},
"persistent": true
},
"custom.view": {
@ -307,6 +81,10 @@
"persistent": true
},
"params.tagProps": {
"onChange": {
"enabled": null,
"script": "\ttagPath \u003d currentValue.value[0].value #I know this looks ugly\t\t\t\n\tstatus \u003d autStand.devices.get_single_device_status(self, tagPath)\n\tself.view.custom.state \u003d status\n\n"
},
"paramDirection": "input",
"persistent": true
},

View File

@ -594,7 +594,7 @@
"dom": {
"onClick": {
"config": {
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props)\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props, section \u003d \"conveyor\")\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
},
"scope": "G",
"type": "script"

View File

@ -443,7 +443,7 @@
"basis": "32px"
},
"props": {
"text": 20,
"text": 6,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -472,7 +472,7 @@
"basis": "32px"
},
"props": {
"text": 2,
"text": 1,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -501,7 +501,7 @@
"basis": "32px"
},
"props": {
"text": 5,
"text": 2,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -559,7 +559,7 @@
"basis": "32px"
},
"props": {
"text": 27,
"text": 9,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -625,9 +625,9 @@
"counts": {
"Critical": 0,
"Diagnostic": 0,
"High": 20,
"Low": 5,
"Medium": 2
"High": 6,
"Low": 2,
"Medium": 1
}
},
"events": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -0,0 +1,77 @@
{
"custom": {},
"params": {
"text": "Provide for a sufficient cooling of the device."
},
"propConfig": {
"params.text": {
"paramDirection": "input",
"persistent": true
}
},
"props": {
"defaultSize": {
"height": 50,
"width": 300
}
},
"root": {
"children": [
{
"meta": {
"name": "Icon"
},
"position": {
"basis": "20px",
"shrink": 0
},
"props": {
"path": "material/brightness_1",
"style": {
"color": "black",
"marginLeft": 4,
"marginTop": 4
}
},
"type": "ia.display.icon"
},
{
"meta": {
"name": "Label"
},
"position": {
"grow": 1
},
"propConfig": {
"props.text": {
"binding": {
"config": {
"path": "view.params.text"
},
"type": "property"
}
}
},
"props": {
"textStyle": {
"color": "black",
"fontSize": "18px"
}
},
"type": "ia.display.label"
}
],
"meta": {
"name": "root"
},
"props": {
"alignContent": "center",
"alignItems": "flex-start",
"justify": "space-evenly",
"style": {
"gap": 7
}
},
"type": "ia.container.flex"
}
}

View File

@ -3,7 +3,7 @@
"color": "#000000",
"deviceName": "S03_CH115_EN",
"priority": "No Active Alarms",
"state": "Offline"
"state": "Closed"
},
"params": {
"demoColor": -1,

View File

@ -3,7 +3,7 @@
"beacon": 0,
"flashingColor": "#808080",
"solidColor": "#FF8C00",
"state": "Offline"
"state": "OFF"
},
"params": {
"demoColor": "",

View File

@ -3,7 +3,7 @@
"PLC": "MCM01",
"color": "#C2C2C2",
"showTags": true,
"state": "Offline"
"state": "Closed"
},
"params": {
"devices": [],

View File

@ -1,6 +1,6 @@
{
"custom": {
"color": "#000",
"color": "#f9050d",
"modifiedTag": "System/MCM01/VFD/UL14_1_VFD1",
"priority": "No Active Alarms"
},

View File

@ -3,12 +3,54 @@
"PLC": "MCM01",
"amperage": 0,
"device": "UL15_1_VFD1",
"faultDescription": "",
"faultProbableCause": [
{
"instancePosition": {},
"instanceStyle": {},
"text": "Ambient temperature too high."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "Fan or ventilation slots are polluted."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "Fan is defective."
},
"value",
"value",
"value",
"value"
],
"faultRemedy": [
{
"instancePosition": {},
"instanceStyle": {},
"text": "Provide for a sufficient cooling of the device."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "Clean fan and ventilation slots."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "If required, replace fan."
},
"value",
"value",
"value"
],
"fpm": 0,
"frequency": 0,
"lastFaultCode": 0,
"maintance_mode": false,
"showTags": true,
"state": "Offline",
"state": "FAULTED/DISCONNECTED",
"statusCode": 0,
"voltage": 0
},
@ -79,6 +121,77 @@
},
"persistent": true
},
"custom.faultDescription": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/Fault/Fault_Description"
},
"transforms": [
{
"expression": "coalesce({value},\"\")",
"type": "expression"
}
],
"type": "tag"
},
"persistent": true
},
"custom.faultProbableCause": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/Fault/Fault_Probable_Cause"
},
"transforms": [
{
"expression": "coalesce({value},\"\")",
"type": "expression"
},
{
"code": "\t# Input: a string like \"Ambient temperature too high. || Fan or ventilation slots are polluted. || Fan is defective.\"\n\t# Output: a list of instance dicts for the repeater\n\t\n\titems \u003d []\n\t\n\ttry:\n\t text \u003d str(value).strip()\n\t if text:\n\t # Split by \"||\" and clean up\n\t parts \u003d [p.strip() for p in text.split(\"||\") if p.strip()]\n\t \n\t # Build instance dictionaries\n\t for p in parts:\n\t items.append({\n\t \"instanceStyle\": {},\n\t \"instancePosition\": {},\n\t \"text\": p\n\t })\n\texcept Exception as e:\n\t system.perspective.print(\"FaultItem repeater transform error: \" + str(e))\n\t items \u003d []\n\t\n\treturn items",
"type": "script"
}
],
"type": "tag"
},
"persistent": true
},
"custom.faultRemedy": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/Fault/Fault_Remedy"
},
"transforms": [
{
"expression": "coalesce({value},\"\")",
"type": "expression"
},
{
"code": "\t# Input: a string like \"Ambient temperature too high. || Fan or ventilation slots are polluted. || Fan is defective.\"\n\t# Output: a list of instance dicts for the repeater\n\t\n\titems \u003d []\n\t\n\ttry:\n\t text \u003d str(value).strip()\n\t if text:\n\t # Split by \"||\" and clean up\n\t parts \u003d [p.strip() for p in text.split(\"||\") if p.strip()]\n\t \n\t # Build instance dictionaries\n\t for p in parts:\n\t items.append({\n\t \"instanceStyle\": {},\n\t \"instancePosition\": {},\n\t \"text\": p\n\t })\n\texcept Exception as e:\n\t system.perspective.print(\"FaultItem repeater transform error: \" + str(e))\n\t items \u003d []\n\t\n\treturn items",
"type": "script"
}
],
"type": "tag"
},
"persistent": true
},
"custom.fpm": {
"binding": {
"config": {
@ -274,6 +387,7 @@
},
"props": {
"defaultSize": {
"height": 1080,
"width": 600
}
},
@ -935,7 +1049,7 @@
"height": 161,
"width": 98,
"x": 97.65,
"y": 505.33000000000004
"y": 455.33
},
"propConfig": {
"props.style.backgroundColor": {
@ -975,7 +1089,7 @@
"height": 92,
"width": 314,
"x": 205.65,
"y": 505.30999999999995
"y": 455.31
},
"propConfig": {
"props.style.backgroundColor": {
@ -1015,7 +1129,7 @@
"height": 64,
"width": 98,
"x": 205.296,
"y": 602.3
"y": 552.3
},
"propConfig": {
"props.enabled": {
@ -1063,7 +1177,7 @@
"height": 64,
"width": 98,
"x": 314.65,
"y": 602.29
"y": 552.29
},
"propConfig": {
"props.enabled": {
@ -1111,7 +1225,7 @@
"height": 64,
"width": 98,
"x": 421.296,
"y": 602.249
"y": 552.249
},
"propConfig": {
"props.enabled": {
@ -1259,6 +1373,231 @@
"y": 381.49
},
"type": "ia.container.flex"
},
{
"children": [
{
"meta": {
"name": "Label"
},
"position": {
"basis": "32px"
},
"propConfig": {
"props.text": {
"binding": {
"config": {
"expression": "\"LENZE FAULTED : \" + {view.custom.faultDescription}"
},
"type": "expr"
}
}
},
"props": {
"style": {
"color": "black",
"paddingLeft": "20px"
}
},
"type": "ia.display.label"
},
{
"children": [
{
"children": [
{
"meta": {
"name": "Label"
},
"position": {
"basis": "33px",
"shrink": 0
},
"props": {
"style": {
"background": "#d9d9d9",
"color": "#000000"
},
"text": "Cause",
"textStyle": {
"textAlign": "center"
}
},
"type": "ia.display.label"
},
{
"children": [
{
"meta": {
"name": "FlexRepeater"
},
"position": {
"basis": "100%"
},
"propConfig": {
"props.instances": {
"binding": {
"config": {
"path": "view.custom.faultProbableCause"
},
"type": "property"
}
}
},
"props": {
"direction": "column",
"path": "autStand/Equipment/VFD-Views/FaultItem",
"style": {
"overflowX": "hidden"
},
"useDefaultViewWidth": false
},
"type": "ia.display.flex-repeater"
}
],
"meta": {
"name": "FlexContainer"
},
"position": {
"basis": "200px",
"grow": 1
},
"props": {
"direction": "column",
"style": {
"borderRight": "solid black 0.5px",
"overflow": "visible"
}
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer"
},
"position": {
"basis": "277px"
},
"props": {
"direction": "column"
},
"type": "ia.container.flex"
},
{
"children": [
{
"meta": {
"name": "Label"
},
"position": {
"basis": "33px",
"shrink": 0
},
"props": {
"style": {
"background": "#d9d9d9",
"color": "#000000"
},
"text": "Remedy",
"textStyle": {
"textAlign": "center"
}
},
"type": "ia.display.label"
},
{
"children": [
{
"meta": {
"name": "FlexRepeater"
},
"position": {
"basis": "100%"
},
"propConfig": {
"props.instances": {
"binding": {
"config": {
"path": "view.custom.faultRemedy"
},
"type": "property"
}
}
},
"props": {
"direction": "column",
"path": "autStand/Equipment/VFD-Views/FaultItem",
"style": {
"overflowX": "hidden"
},
"useDefaultViewWidth": false
},
"type": "ia.display.flex-repeater"
}
],
"meta": {
"name": "FlexContainer"
},
"position": {
"basis": "200px",
"grow": 1
},
"props": {
"direction": "column",
"style": {
"overflow": "visible"
}
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer_0"
},
"position": {
"basis": "277px"
},
"props": {
"direction": "column",
"style": {
"borderLeft": "solid black 0.5px"
}
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer_8"
},
"position": {
"grow": 1
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer_2"
},
"position": {
"height": 300,
"width": 508,
"x": 42.33000093823242,
"y": 642.33
},
"propConfig": {
"meta.visible": {
"binding": {
"config": {
"expression": "{view.custom.faultDescription} !\u003d \"\""
},
"type": "expr"
}
}
},
"props": {
"direction": "column"
},
"type": "ia.container.flex"
}
],
"meta": {

View File

@ -1,8 +1,8 @@
{
"custom": {
"color": "#000000",
"color": "#AAAAAA",
"deviceName": "S03_CH101_PRX1",
"state": "Offline"
"state": "INACTIVE"
},
"params": {
"demoColor": -1,

View File

@ -571,7 +571,7 @@
"dom": {
"onClick": {
"config": {
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props)\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props, section \u003d \"conveyor\")\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
},
"scope": "G",
"type": "script"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

View File

@ -4,7 +4,7 @@
"$": [
"ds",
192,
1762166893089
1762422327752
],
"$columns": [
{
@ -31,11 +31,11 @@
},
{
"data": [
20,
5,
6,
2,
36,
3
1,
32,
4
],
"name": "Count",
"type": "Long"

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -3,16 +3,16 @@
"counts": {
"Critical": 0,
"Diagnostic": 0,
"High": 20,
"Low": 5,
"Medium": 2,
"Total": 27
"High": 6,
"Low": 2,
"Medium": 1,
"Total": 9
},
"totalAlarms": {
"$": [
"ds",
192,
1762166890980
1762422326706
],
"$columns": [
{
@ -39,11 +39,11 @@
},
{
"data": [
20,
5,
6,
2,
36,
3
1,
32,
4
],
"name": "Count",
"type": "Long"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -4,7 +4,7 @@
"isHighlited": false,
"overlayColor": "#ffffff",
"priority": "No Active Alarms",
"state": "Offline"
"state": "BLOCKED"
},
"params": {
"demoColor": -1,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,111 @@
{
"custom": {},
"params": {},
"props": {
"defaultSize": {
"height": 920
}
},
"root": {
"children": [
{
"meta": {
"name": "PDFViewer"
},
"position": {
"height": 1,
"width": 1
},
"propConfig": {
"props.page": {
"onChange": {
"enabled": null,
"script": "\tmaxCount \u003d self.props.pageCount\n\tcurrentPage \u003d currentValue.value\n\tif currentPage \u003c 1:\n\t\tself.props.page \u003d maxCount\n\t\n\tif currentPage \u003e maxCount:\n\t\tself.props.page \u003d 1"
}
}
},
"props": {
"page": 1,
"pageCount": 32,
"source": "\\Description of Operations - Amazon BNA8.pdf",
"style": {
"overflow": "hidden"
}
},
"type": "ia.display.pdf-viewer"
},
{
"events": {
"dom": {
"onClick": {
"config": {
"script": "\tself.getSibling(\"PDFViewer\").props.page -\u003d1"
},
"scope": "G",
"type": "script"
}
}
},
"meta": {
"name": "Icon_0",
"tooltip": {
"enabled": true,
"text": "Previous Page"
}
},
"position": {
"height": 0.043,
"width": 0.0587,
"x": 0.4217,
"y": 0.9451
},
"props": {
"path": "material/arrow_back",
"style": {
"cursor": "pointer"
}
},
"type": "ia.display.icon"
},
{
"events": {
"dom": {
"onClick": {
"config": {
"script": "\tself.getSibling(\"PDFViewer\").props.page +\u003d1"
},
"scope": "G",
"type": "script"
}
}
},
"meta": {
"name": "Icon_1",
"tooltip": {
"text": "Next Page"
}
},
"position": {
"height": 0.043,
"width": 0.0587,
"x": 0.5439,
"y": 0.9451
},
"props": {
"path": "material/arrow_forward",
"style": {
"cursor": "painter"
}
},
"type": "ia.display.icon"
}
],
"meta": {
"name": "root"
},
"props": {
"mode": "percent"
},
"type": "ia.container.coord"
}
}

View File

@ -3562,7 +3562,7 @@
"height": 0.0231,
"width": 0.013,
"x": 0.0721,
"y": 0.4037
"y": 0.3893
},
"props": {
"params": {
@ -10069,7 +10069,7 @@
},
{
"meta": {
"name": "PS3_12_TPE1"
"name": "PS3_12_TPE2"
},
"position": {
"height": 0.0231,
@ -10083,7 +10083,7 @@
"props": {
"params": {
"tagProps": [
"System/MCM02/PE/TPE/PS3_12_TPE1",
"System/MCM02/PE/TPE/PS3_12_TPE2",
"value",
"value",
"value",
@ -10321,6 +10321,78 @@
}
},
"type": "ia.display.view"
},
{
"meta": {
"name": "PS3_13_ENW2"
},
"position": {
"height": 0.0231,
"width": 0.0131,
"x": 0.0488,
"y": 0.4583
},
"props": {
"params": {
"tagProps": [
"System/MCM02/ENCODER/ENW/PS3_12_ENW1",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value"
]
},
"path": "autStand/Equipment/Encoder",
"style": {
"borderRadius": "50%",
"classes": "hover",
"overflow": "visible"
}
},
"type": "ia.display.view"
},
{
"meta": {
"name": "PS3_12_TPE1"
},
"position": {
"height": 0.0231,
"rotate": {
"angle": "90deg"
},
"width": 0.0531,
"x": 0.0543,
"y": 0.4473
},
"props": {
"params": {
"tagProps": [
"System/MCM02/PE/TPE/PS3_12_TPE1",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value"
]
},
"path": "autStand/Equipment/Photoeye_Chute",
"style": {
"classes": "hover-90",
"overflow": "visible"
},
"useDefaultViewHeight": true,
"useDefaultViewWidth": true
},
"type": "ia.display.view"
}
],
"meta": {

View File

@ -4,7 +4,7 @@
"divertingLeft": false,
"divertingRight": false,
"priority": "No Active Alarms",
"state": "Offline"
"state": "Closed"
},
"params": {
"demoColor": 0,

View File

@ -574,7 +574,7 @@
"dom": {
"onClick": {
"config": {
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props)\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props, section \u003d \"conveyor\")\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
},
"scope": "G",
"type": "script"

View File

@ -0,0 +1,542 @@
import os, json, sys
# ======================================================
# Helper Function: State Resolver
# ======================================================
def get_device_state(value, tagPath):
up = tagPath.upper()
# === Base state dictionary ===
state_mappings = {
0: "Closed",
1: "Actuated",
2: "Communication Faulted",
3: "Conveyor Running In Maintenance Mode",
4: "Disabled",
5: "Disconnected",
6: "Stopped",
7: "Enabled Not Running",
8: "Encoder Fault",
9: "Energy Management",
10: "ESTOP Was Actuated",
11: "EStopped",
12: "EStopped Locally",
13: "Extended Faulted",
14: "Full",
15: "Gaylord Start Pressed",
16: "Jam Fault",
17: "Jammed",
18: "Loading Allowed",
19: "Loading Not Allowed",
20: "Low Air Pressure Fault Was Present",
21: "Maintenance Mode",
22: "Conveyor Stopped In Maintenance Mode",
23: "Motor Faulted",
24: "Motor Was Faulted",
25: "Normal",
26: "Off Inactive",
27: "Open",
28: "PLC Ready To Run",
29: "Package Release Pressed",
30: "Power Branch Was Faulted",
31: "Pressed",
32: "Ready To Receive",
33: "Running",
34: "Started",
35: "Stopped",
36: "System Started",
37: "Unknown",
38: "VFD Fault",
39: "Conveyor Running In Power Saving Mode",
40: "Conveyor Jogging In Maintenance Mode",
41: "VFD Reset Required",
42: "Jam Reset Push Button Pressed",
43: "Start Push Button Pressed",
44: "Stop Push Button Pressed",
45: "No Container",
46: "Ready To Be Enabled",
47: "Half Full",
48: "Enabled",
49: "Tipper Faulted",
50: "OK",
51: "Disconnected",
52: "Faulted",
53: "Faulted/Disconnect",
54: "Diverting"
}
# === TPE (Tracking Photoeye) ===
if "/TPE/" in up:
if value == 0:
return "Blocked"
elif value == 27:
return "Clear"
elif value == 17:
return "Jammed"
else:
return state_mappings.get(value, "Unknown")
# === Single Photoeyes (PE1, PE2) ===
if up.endswith(("PE1", "PE2")):
if not value:
return "Clear"
else:
return "Blocked"
# === Prox Sensors (PRX1, PRX2) ===
if up.endswith(("PRX1", "PRX2")):
if not value:
return "Inactive"
else:
return "Actuated"
# === Beacons (BCN) ===
if "/BEACON" in up:
if value == 0:
return "Off"
elif value == 1:
return "Cleared / Reset Required"
else:
return "Active"
# === Default ===
return state_mappings.get(value, "Unknown")
# ======================================================
# Helper Function: Read One Device (multi or single state)
# ======================================================
def read_device_status(tagPath, provider, dev_name):
"""
Reads the appropriate state tag(s) for a given device and returns
a list of {Device, Status} dictionaries.
Handles multi-state (SS), VFD, PRX, PE, EN, etc.
"""
rows = []
try:
up = tagPath.upper()
# === Case 0: SS (Start/Stop Station) ===
if up.endswith("SS") or "/SS/" in up:
for sub in ("Start", "Stop"):
sub_path = provider + tagPath + "/" + sub + "/State"
try:
result = system.tag.readBlocking([sub_path])[0]
if result.quality.isGood():
status_value = get_device_state(result.value, tagPath)
else:
status_value = "Unknown"
except:
status_value = "Unknown"
rows.append({
"Device": "{} ({})".format(dev_name, sub),
"Status": status_value
})
return rows # handled fully
# === Case 1: VFD / Conveyor ===
if "/VFD/" in up:
path = provider + tagPath + "/Drive/Lenze"
# === Case 2: Chute sensors (PE / PRX) ===
elif up.endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in up:
path = provider + tagPath
# === Case 3: Chute EN ===
elif up.endswith("EN") and "/CHUTE/" in up:
path = provider + tagPath + "_State"
# === Case 4: Default ===
else:
path = provider + tagPath + "/State"
try:
result = system.tag.readBlocking([path])[0]
if result.quality.isGood():
status_value = get_device_state(result.value, tagPath)
else:
status_value = "Offline"
except:
status_value = "Offline"
rows.append({
"Device": dev_name,
"Status": status_value
})
except Exception as e:
system.perspective.print("Error reading device status for %s: %s" % (dev_name, e))
return rows
# ======================================================
# Helper Function: Single Device Reader (for Docked Device View)
# ======================================================
def get_single_device_status(self, tagPath):
"""
Reads a single device tag (used for docked device views).
Returns a single readable status string (e.g. "Running", "Blocked", etc.)
"""
try:
up = tagPath.upper()
provider = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
if up.endswith("SS") or "/SS/" in up:
states = []
for sub in ("Start", "Stop"):
sub_path = provider + tagPath + "/" + sub + "/State"
try:
result = system.tag.readBlocking([sub_path])[0]
if result.quality.isGood():
states.append("{}: {}".format(sub, get_device_state(result.value, tagPath)))
else:
states.append("{}: Unknown".format(sub))
except:
states.append("{}: Unknown".format(sub))
return " | ".join(states)
# === VFD ===
if "/VFD/" in up:
path = provider + tagPath + "/Drive/Lenze"
# === Sensors ===
elif up.endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in up:
path = provider + tagPath
# === EN ===
elif up.endswith("EN") and "/CHUTE/" in up:
path = provider + tagPath + "_State"
else:
path = provider + tagPath + "/State"
result = system.tag.readBlocking([path])[0]
if result.quality.isGood():
return get_device_state(result.value, tagPath)
else:
return "Unknown"
except Exception as e:
system.perspective.print("Error reading single device status for %s: %s" % (tagPath, e))
return "Unknown"
# ======================================================
# Device Mapping Builder
# ======================================================
global_device_mapping = {}
def build_device_mapping(full_tag_path):
"""
Builds global_device_mapping for devices under the same PLC and parent device.
Adds support for:
- Chute FIOM devices (e.g. S03_CH109_FIOM_1 when clicking S03_CH109)
- Shared JR and PE devices used by multiple chutes (e.g. S03_1_JR1, S03_1_LRPE1)
"""
system.perspective.print(full_tag_path)
global global_device_mapping
global_device_mapping.clear()
try:
path_parts = full_tag_path.split("/")
plc_name = path_parts[1] if len(path_parts) > 1 else path_parts[0]
clicked_name = path_parts[-1] if len(path_parts) > 0 else ""
# --- Clean clicked name ---
if "_VFD" in clicked_name:
clicked_name = clicked_name.split("_VFD")[0]
project_name = system.util.getProjectName()
base_path = (
os.getcwd().replace("\\", "/")
+ "/data/projects/"
+ project_name
+ "/com.inductiveautomation.perspective/Views/autStand/Detailed_Views/MCM-Views"
)
if not os.path.exists(base_path):
system.perspective.print("Path not found: " + base_path)
return {}
# --- Detect if this is a Chute ---
is_chute = "/CHUTE/" in full_tag_path.upper()
for view_folder in os.listdir(base_path):
json_file = os.path.join(base_path, view_folder, "view.json")
if not os.path.isfile(json_file):
continue
try:
with open(json_file, "r") as fh:
view_json = json.load(fh)
except Exception:
continue
root_children = (view_json.get("root") or {}).get("children") or []
if not root_children:
continue
container = root_children[0]
children = container.get("children") or []
for child in children:
props = child.get("props") or {}
params = props.get("params") or {}
tag_props = params.get("tagProps")
if isinstance(tag_props, list) and len(tag_props) > 0:
tag_prop = str(tag_props[0])
parts = tag_prop.split("/")
if len(parts) > 1 and parts[1] == plc_name:
dev_name = parts[-1]
if len(parts) > 3 and parts[-2] == clicked_name:
dev_name = clicked_name + "_" + parts[-1]
else:
dev_name = parts[-1]
prefix = clicked_name + "_"
# === 🟢 NEW: Chute FIOM match ===
if is_chute and dev_name.startswith(clicked_name + "_"):
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
continue
# === Default inclusion ===
if dev_name.startswith(prefix) or (len(parts) > 3 and parts[-2] == clicked_name):
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
# === Special Case: JR Buttons ===
elif "/JR/" in tag_prop.upper():
try:
jr_parts = tag_prop.split("/JR/")
if len(jr_parts) > 1:
sub_path = jr_parts[1]
if sub_path.startswith(clicked_name + "_JR"):
dev_name = sub_path.split("/")[0]
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
except:
pass
shared_jr_pe_map = {
"S03_CH101": ["S03_1_JR1", "S03_1_LRPE1"],
"S03_CH103": ["S03_1_JR1", "S03_1_LRPE1"],
"S03_CH105": ["S03_1_JR1", "S03_1_LRPE1"],
"S03_CH107": ["S03_1_JR3", "S03_1_LRPE3"],
"S03_CH108": ["S03_1_JR4", "S03_1_LRPE4"],
"S03_CH109": ["S03_1_JR3", "S03_1_LRPE3"],
"S03_CH110": ["S03_1_JR4", "S03_1_LRPE4"],
"S03_CH111": ["S03_1_JR3", "S03_1_LRPE3"],
"S03_CH112": ["S03_1_JR2", "S03_1_LRPE2"],
"S03_CH113": ["S03_1_JR5", "S03_1_LRPE5"],
"S03_CH114": ["S03_1_JR6", "S03_1_LRPE6"],
"S03_CH115": ["S03_1_JR5", "S03_1_LRPE5"],
"S03_CH116": ["S03_1_JR6", "S03_1_LRPE6"],
"S03_CH117": ["S03_1_JR5", "S03_1_LRPE5"],
"S03_CH118": ["S03_1_JR6", "S03_1_LRPE6"],
"S03_CH119": ["S03_1_JR7", "S03_1_LRPE7"],
"S03_CH120": ["S03_1_JR8", "S03_1_LRPE8"],
"S03_CH121": ["S03_1_JR7", "S03_1_LRPE7"],
"S03_CH122": ["S03_1_JR8", "S03_1_LRPE8"],
"S03_CH123": ["S03_1_JR7", "S03_1_LRPE7"],
"S03_CH124": ["S03_1_JR8", "S03_1_LRPE8"],
}
shared_fiom_map = {
"NCS1_1": ["S03_1_FIOM_5", "S03_1_FIOM_9", "S03_1_FIOM_1", "S03_1_FIOM_2","S03_1_FIOM_3","S03_1_FIOM_4", "S03_1_FIOM_6","S03_1_FIOM_7", "S03_1_FIOM_8"],
}
if clicked_name in shared_jr_pe_map:
extra_devices = shared_jr_pe_map[clicked_name]
for dev in extra_devices:
try:
# Base tag (for PE)
base_tag = "System/MCM02/Station/Chute_JR/" + dev
# JR subtag (for JR button)
jr_tag = base_tag + "/JR" if dev.endswith("JR1") else base_tag
for tag_candidate in [base_tag, jr_tag]:
global_device_mapping[dev] = {
"tagPath": tag_candidate,
"zone": "Chute_JR"
}
except Exception as ex:
system.perspective.print("Error adding JR/PE for {}: {}".format(clicked_name, ex))
if clicked_name in shared_fiom_map:
for dev in shared_fiom_map[clicked_name]:
tag_path = "System/{}/IO_Block/FIO/{}".format(plc_name, dev)
global_device_mapping[dev] = {
"tagPath": tag_path,
"zone": "FIO"
}
return global_device_mapping
except Exception as e:
whid = "unknown"
try:
whid = system.tag.readBlocking("Configuration/FC")[0].value
except:
pass
logger = system.util.getLogger("%s-build_device_mapping" % whid)
exc_type, exc_obj, tb = sys.exc_info()
logger.error("Error at line %s: %s" % (tb.tb_lineno, exc_obj))
return {}
# ======================================================
# Device Table Builder
# ======================================================
def build_device_table(self):
rows = []
try:
for dev_name, info in global_device_mapping.items():
tagPath = info.get("tagPath", "")
if not tagPath:
continue
provider = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
rows.extend(read_device_status(tagPath, provider, dev_name))
return rows
except Exception as e:
system.perspective.print("Error building device table: %s" % e)
return []
# ======================================================
# Get All Tags for Clicked Device
# ======================================================
def getAllTags(self, tagPath, section="all"):
"""
Reads all tags under a UDT instance (recursively)
and returns a list of dictionaries:
[
{"Name": "State", "OPC Path": "System/MCM01/...", "Value": "Running"},
...
]
"""
rows = []
try:
providerPath = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
driveFolderName = "Drive"
# === Utility: read a single atomic tag ===
def readSingleTag(path, prefix=""):
try:
result = system.tag.readBlocking([providerPath + path])[0]
value = str(result.value) if result.quality.isGood() else "Unknown"
except:
value = "Unknown"
displayName = prefix + path.split("/")[-1] if prefix else path.split("/")[-1]
rows.append({
"Name": displayName,
"OPC Path": path,
"Value": value
})
# === Utility: recursive browse ===
def browseRecursive(basePath, prefix=""):
children = system.tag.browse(providerPath + basePath).getResults()
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
fullPath = str(child.get("fullPath", ""))
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
# --- Conveyor filter (skip Drive folder) ---
if section == "conveyor" and name == driveFolderName:
continue
if tagType == "Folder":
# --- Skip JR subfolder if current device is LRPE ---
if name.upper() == "JR" and "_JR" in basePath.upper():
continue
newPrefix = prefix + name + "/" if prefix else name + "/"
browseRecursive(basePath + "/" + name, newPrefix)
elif tagType == "AtomicTag":
readSingleTag(fullPath, prefix)
# === MAIN ENTRY POINT ===
# --- Case 1: VFD ---
if section == "vfd":
drivePath = tagPath + "/" + driveFolderName
browseRecursive(drivePath)
# --- Case 2: Flat EN_ tags (Chutes) ---
elif tagPath.upper().endswith("/EN"):
parentPath = "/".join(tagPath.split("/")[:-1])
children = system.tag.browse(providerPath + parentPath).getResults()
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
if tagType == "AtomicTag" and name.upper().startswith("EN_"):
fullPath = str(child.get("fullPath", ""))
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
readSingleTag(fullPath)
# --- Case 3: Single Sensors (PE/PRX) ---
elif tagPath.upper().endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in tagPath.upper():
readSingleTag(tagPath)
# --- Case 4: Default / Fallback ---
else:
browseResult = system.tag.browse(providerPath + tagPath).getResults()
if not browseResult:
# Possibly a struct-style UDT (like some TPEs)
system.perspective.print("Empty browse for {}, checking struct value...".format(tagPath))
try:
result = system.tag.readBlocking([providerPath + tagPath])[0]
value = result.value
# === Expand STRUCT ===
if isinstance(value, dict):
system.perspective.print("Detected STRUCT value, expanding {}".format(tagPath))
def flattenStruct(struct, base=""):
for k, v in struct.items():
newName = base + "/" + k if base else k
if isinstance(v, dict):
flattenStruct(v, newName)
else:
rows.append({
"Name": newName,
"OPC Path": tagPath + "/" + newName,
"Value": str(v)
})
flattenStruct(value)
else:
# Not a struct, read normally
readSingleTag(tagPath)
except Exception as ex:
system.perspective.print("Fallback read failed for {}: {}".format(tagPath, ex))
else:
# Normal browse case
browseRecursive(tagPath)
return rows
except Exception as e:
system.perspective.print("Error in getAllTags: {}".format(e))
return []

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-04T15:20:44Z"
"timestamp": "2025-11-05T14:27:38Z"
},
"lastModificationSignature": "7aefd1d334d282d738eaf97f9245f751ec78e796123dc60f1c7cf6f0df39e039"
"lastModificationSignature": "de4d878ca08fcb3581ac8deb45ba0df2b6cfa9e6e54a588bef5c7089f40a3524"
}
}

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:10:15Z"
"timestamp": "2025-11-06T09:07:51Z"
},
"lastModificationSignature": "93ca78bdef1d5678a8d5ec0bdb7adb2d459d940aa32e842ff0e0bedb902aad35"
"lastModificationSignature": "69538fe7e0bc707101c3e0349c42569d245b26f2d01e548dc6d6409daded6bad"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -3562,7 +3562,7 @@
"height": 0.0231,
"width": 0.013,
"x": 0.0721,
"y": 0.4037
"y": 0.3893
},
"props": {
"params": {
@ -10069,7 +10069,7 @@
},
{
"meta": {
"name": "PS3_12_TPE1"
"name": "PS3_12_TPE2"
},
"position": {
"height": 0.0231,
@ -10083,7 +10083,7 @@
"props": {
"params": {
"tagProps": [
"System/MCM02/PE/TPE/PS3_12_TPE1",
"System/MCM02/PE/TPE/PS3_12_TPE2",
"value",
"value",
"value",
@ -10321,6 +10321,78 @@
}
},
"type": "ia.display.view"
},
{
"meta": {
"name": "PS3_13_ENW2"
},
"position": {
"height": 0.0231,
"width": 0.0131,
"x": 0.0488,
"y": 0.4583
},
"props": {
"params": {
"tagProps": [
"System/MCM02/ENCODER/ENW/PS3_12_ENW1",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value"
]
},
"path": "autStand/Equipment/Encoder",
"style": {
"borderRadius": "50%",
"classes": "hover",
"overflow": "visible"
}
},
"type": "ia.display.view"
},
{
"meta": {
"name": "PS3_12_TPE1"
},
"position": {
"height": 0.0231,
"rotate": {
"angle": "90deg"
},
"width": 0.0531,
"x": 0.0543,
"y": 0.4473
},
"props": {
"params": {
"tagProps": [
"System/MCM02/PE/TPE/PS3_12_TPE1",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value",
"value"
]
},
"path": "autStand/Equipment/Photoeye_Chute",
"style": {
"classes": "hover-90",
"overflow": "visible"
},
"useDefaultViewHeight": true,
"useDefaultViewWidth": true
},
"type": "ia.display.view"
}
],
"meta": {

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:19:56Z"
"timestamp": "2025-11-06T07:10:44Z"
},
"lastModificationSignature": "43d1be0664c71a5e4d3d4aabd47c7de302c43a5c16b73253ee224b331c75f8b0"
"lastModificationSignature": "3ec9d44b3da00172f7274ea7375a30869247ebe90d8164e8382a4589a7b8c543"
}
}

View File

@ -3,7 +3,7 @@
"PLC": "MCM01",
"color": "#C2C2C2",
"showTags": true,
"state": "Offline"
"state": "Closed"
},
"params": {
"devices": [],

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:10:15Z"
"timestamp": "2025-11-06T06:30:46Z"
},
"lastModificationSignature": "b2d0d45ff1c80965505c780bd723721a4160fa8eb9dd6d8e8b303967a53cb7a3"
"lastModificationSignature": "d10ceafdc901b383a0a4411bd8d28abd097f520027017a0482d5ec2e2ac1ed94"
}
}

View File

@ -71,232 +71,6 @@
"persistent": true
},
"custom.state": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/State"
},
"transforms": [
{
"expression": "coalesce({value},-1)",
"type": "expression"
},
{
"fallback": "Offline",
"inputType": "scalar",
"mappings": [
{
"input": 0,
"output": "Closed"
},
{
"input": 1,
"output": "Actuated"
},
{
"input": 2,
"output": "Communication Faulted"
},
{
"input": 3,
"output": "Conveyor Running In Maintenance Mode"
},
{
"input": 4,
"output": "Disabled"
},
{
"input": 5,
"output": "Disconnected"
},
{
"input": 6,
"output": "Stopped"
},
{
"input": 7,
"output": "Enabled Not Running"
},
{
"input": 8,
"output": "Encoder Fault"
},
{
"input": 9,
"output": "Energy Management"
},
{
"input": 10,
"output": "ESTOP Was Actuated"
},
{
"input": 11,
"output": "EStopped"
},
{
"input": 12,
"output": "EStopped Locally"
},
{
"input": 13,
"output": "Extended Faulted"
},
{
"input": 14,
"output": "Full"
},
{
"input": 15,
"output": "Gaylord Start Pressed"
},
{
"input": 16,
"output": "Jam Fault"
},
{
"input": 17,
"output": "Jammed"
},
{
"input": 18,
"output": "Loading Allowed"
},
{
"input": 19,
"output": "Loading Not Allowed"
},
{
"input": 20,
"output": "Low Air Pressure Fault Was Present"
},
{
"input": 21,
"output": "Maintenance Mode"
},
{
"input": 22,
"output": "Conveyor Stopped In Maintenance Mode"
},
{
"input": 23,
"output": "Motor Faulted"
},
{
"input": 24,
"output": "Motor Was Faulted"
},
{
"input": 25,
"output": "Normal"
},
{
"input": 26,
"output": "Off Inactive"
},
{
"input": 27,
"output": "Open"
},
{
"input": 28,
"output": "PLC Ready To Run"
},
{
"input": 29,
"output": "Package Release Pressed"
},
{
"input": 30,
"output": "Power Branch Was Faulted"
},
{
"input": 31,
"output": "Pressed"
},
{
"input": 32,
"output": "Ready To Receive"
},
{
"input": 33,
"output": "Running"
},
{
"input": 34,
"output": "Started"
},
{
"input": 35,
"output": "Stopped"
},
{
"input": 36,
"output": "System Started"
},
{
"input": 37,
"output": "Unknown"
},
{
"input": 38,
"output": "VFD Fault"
},
{
"input": 39,
"output": "Conveyor Running In Power Saving Mode"
},
{
"input": 40,
"output": "Conveyor Jogging In Maintenance Mode"
},
{
"input": 41,
"output": "VFD Reset Required"
},
{
"input": 42,
"output": "Jam Reset Push Button Pressed"
},
{
"input": 43,
"output": "Start Push Button Pressed"
},
{
"input": 44,
"output": "Stop Push Button Pressed"
},
{
"input": 45,
"output": "No Container"
},
{
"input": 46,
"output": "Ready To Be Enabled"
},
{
"input": 47,
"output": "Half Full"
},
{
"input": 48,
"output": "Enabled"
},
{
"input": 49,
"output": "Tipper Faulted"
}
],
"outputType": "scalar",
"type": "map"
}
],
"type": "tag"
},
"persistent": true
},
"custom.view": {
@ -307,6 +81,10 @@
"persistent": true
},
"params.tagProps": {
"onChange": {
"enabled": null,
"script": "\ttagPath \u003d currentValue.value[0].value #I know this looks ugly\t\t\t\n\tstatus \u003d autStand.devices.get_single_device_status(self, tagPath)\n\tself.view.custom.state \u003d status\n\n"
},
"paramDirection": "input",
"persistent": true
},

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:06:34Z"
"timestamp": "2025-11-06T10:48:24Z"
},
"lastModificationSignature": "4ecba60a0ac1422450b0014b8931ec222fa721d33dace06608f36c9d4237d607"
"lastModificationSignature": "15f70498ff0d20fac7dd1f3668d7659faf171f84d8964bd1bb19f3a9e6cc0bb3"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -3,12 +3,54 @@
"PLC": "MCM01",
"amperage": 0,
"device": "UL15_1_VFD1",
"faultDescription": "",
"faultProbableCause": [
{
"instancePosition": {},
"instanceStyle": {},
"text": "Ambient temperature too high."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "Fan or ventilation slots are polluted."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "Fan is defective."
},
"value",
"value",
"value",
"value"
],
"faultRemedy": [
{
"instancePosition": {},
"instanceStyle": {},
"text": "Provide for a sufficient cooling of the device."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "Clean fan and ventilation slots."
},
{
"instancePosition": {},
"instanceStyle": {},
"text": "If required, replace fan."
},
"value",
"value",
"value"
],
"fpm": 0,
"frequency": 0,
"lastFaultCode": 0,
"maintance_mode": false,
"showTags": true,
"state": "Offline",
"state": "FAULTED/DISCONNECTED",
"statusCode": 0,
"voltage": 0
},
@ -79,6 +121,77 @@
},
"persistent": true
},
"custom.faultDescription": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/Fault/Fault_Description"
},
"transforms": [
{
"expression": "coalesce({value},\"\")",
"type": "expression"
}
],
"type": "tag"
},
"persistent": true
},
"custom.faultProbableCause": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/Fault/Fault_Probable_Cause"
},
"transforms": [
{
"expression": "coalesce({value},\"\")",
"type": "expression"
},
{
"code": "\t# Input: a string like \"Ambient temperature too high. || Fan or ventilation slots are polluted. || Fan is defective.\"\n\t# Output: a list of instance dicts for the repeater\n\t\n\titems \u003d []\n\t\n\ttry:\n\t text \u003d str(value).strip()\n\t if text:\n\t # Split by \"||\" and clean up\n\t parts \u003d [p.strip() for p in text.split(\"||\") if p.strip()]\n\t \n\t # Build instance dictionaries\n\t for p in parts:\n\t items.append({\n\t \"instanceStyle\": {},\n\t \"instancePosition\": {},\n\t \"text\": p\n\t })\n\texcept Exception as e:\n\t system.perspective.print(\"FaultItem repeater transform error: \" + str(e))\n\t items \u003d []\n\t\n\treturn items",
"type": "script"
}
],
"type": "tag"
},
"persistent": true
},
"custom.faultRemedy": {
"binding": {
"config": {
"fallbackDelay": 2.5,
"mode": "indirect",
"references": {
"0": "{view.params.tagProps[0]}",
"fc": "{session.custom.fc}"
},
"tagPath": "[{fc}_SCADA_TAG_PROVIDER]{0}/Fault/Fault_Remedy"
},
"transforms": [
{
"expression": "coalesce({value},\"\")",
"type": "expression"
},
{
"code": "\t# Input: a string like \"Ambient temperature too high. || Fan or ventilation slots are polluted. || Fan is defective.\"\n\t# Output: a list of instance dicts for the repeater\n\t\n\titems \u003d []\n\t\n\ttry:\n\t text \u003d str(value).strip()\n\t if text:\n\t # Split by \"||\" and clean up\n\t parts \u003d [p.strip() for p in text.split(\"||\") if p.strip()]\n\t \n\t # Build instance dictionaries\n\t for p in parts:\n\t items.append({\n\t \"instanceStyle\": {},\n\t \"instancePosition\": {},\n\t \"text\": p\n\t })\n\texcept Exception as e:\n\t system.perspective.print(\"FaultItem repeater transform error: \" + str(e))\n\t items \u003d []\n\t\n\treturn items",
"type": "script"
}
],
"type": "tag"
},
"persistent": true
},
"custom.fpm": {
"binding": {
"config": {
@ -274,6 +387,7 @@
},
"props": {
"defaultSize": {
"height": 1080,
"width": 600
}
},
@ -935,7 +1049,7 @@
"height": 161,
"width": 98,
"x": 97.65,
"y": 505.33000000000004
"y": 455.33
},
"propConfig": {
"props.style.backgroundColor": {
@ -975,7 +1089,7 @@
"height": 92,
"width": 314,
"x": 205.65,
"y": 505.30999999999995
"y": 455.31
},
"propConfig": {
"props.style.backgroundColor": {
@ -1015,7 +1129,7 @@
"height": 64,
"width": 98,
"x": 205.296,
"y": 602.3
"y": 552.3
},
"propConfig": {
"props.enabled": {
@ -1063,7 +1177,7 @@
"height": 64,
"width": 98,
"x": 314.65,
"y": 602.29
"y": 552.29
},
"propConfig": {
"props.enabled": {
@ -1111,7 +1225,7 @@
"height": 64,
"width": 98,
"x": 421.296,
"y": 602.249
"y": 552.249
},
"propConfig": {
"props.enabled": {
@ -1259,6 +1373,231 @@
"y": 381.49
},
"type": "ia.container.flex"
},
{
"children": [
{
"meta": {
"name": "Label"
},
"position": {
"basis": "32px"
},
"propConfig": {
"props.text": {
"binding": {
"config": {
"expression": "\"LENZE FAULTED : \" + {view.custom.faultDescription}"
},
"type": "expr"
}
}
},
"props": {
"style": {
"color": "black",
"paddingLeft": "20px"
}
},
"type": "ia.display.label"
},
{
"children": [
{
"children": [
{
"meta": {
"name": "Label"
},
"position": {
"basis": "33px",
"shrink": 0
},
"props": {
"style": {
"background": "#d9d9d9",
"color": "#000000"
},
"text": "Cause",
"textStyle": {
"textAlign": "center"
}
},
"type": "ia.display.label"
},
{
"children": [
{
"meta": {
"name": "FlexRepeater"
},
"position": {
"basis": "100%"
},
"propConfig": {
"props.instances": {
"binding": {
"config": {
"path": "view.custom.faultProbableCause"
},
"type": "property"
}
}
},
"props": {
"direction": "column",
"path": "autStand/Equipment/VFD-Views/FaultItem",
"style": {
"overflowX": "hidden"
},
"useDefaultViewWidth": false
},
"type": "ia.display.flex-repeater"
}
],
"meta": {
"name": "FlexContainer"
},
"position": {
"basis": "200px",
"grow": 1
},
"props": {
"direction": "column",
"style": {
"borderRight": "solid black 0.5px",
"overflow": "visible"
}
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer"
},
"position": {
"basis": "277px"
},
"props": {
"direction": "column"
},
"type": "ia.container.flex"
},
{
"children": [
{
"meta": {
"name": "Label"
},
"position": {
"basis": "33px",
"shrink": 0
},
"props": {
"style": {
"background": "#d9d9d9",
"color": "#000000"
},
"text": "Remedy",
"textStyle": {
"textAlign": "center"
}
},
"type": "ia.display.label"
},
{
"children": [
{
"meta": {
"name": "FlexRepeater"
},
"position": {
"basis": "100%"
},
"propConfig": {
"props.instances": {
"binding": {
"config": {
"path": "view.custom.faultRemedy"
},
"type": "property"
}
}
},
"props": {
"direction": "column",
"path": "autStand/Equipment/VFD-Views/FaultItem",
"style": {
"overflowX": "hidden"
},
"useDefaultViewWidth": false
},
"type": "ia.display.flex-repeater"
}
],
"meta": {
"name": "FlexContainer"
},
"position": {
"basis": "200px",
"grow": 1
},
"props": {
"direction": "column",
"style": {
"overflow": "visible"
}
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer_0"
},
"position": {
"basis": "277px"
},
"props": {
"direction": "column",
"style": {
"borderLeft": "solid black 0.5px"
}
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer_8"
},
"position": {
"grow": 1
},
"type": "ia.container.flex"
}
],
"meta": {
"name": "FlexContainer_2"
},
"position": {
"height": 300,
"width": 508,
"x": 42.33000093823242,
"y": 642.33
},
"propConfig": {
"meta.visible": {
"binding": {
"config": {
"expression": "{view.custom.faultDescription} !\u003d \"\""
},
"type": "expr"
}
}
},
"props": {
"direction": "column"
},
"type": "ia.container.flex"
}
],
"meta": {

View File

@ -9,8 +9,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:03:20Z"
"timestamp": "2025-11-05T10:58:14Z"
},
"lastModificationSignature": "a45fc778e3455652a14c8b77b8c627633086bef4b0984bff774dc9ebdf86e057"
"lastModificationSignature": "71bfb1fcb21970bcad2d212abf101e99ef7649061deac3f5f19a56201c32172e"
}
}

View File

@ -3,7 +3,7 @@
"beacon": 0,
"flashingColor": "#808080",
"solidColor": "#FF8C00",
"state": "Offline"
"state": "OFF"
},
"params": {
"demoColor": "",

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:09:30Z"
"timestamp": "2025-11-05T10:58:25Z"
},
"lastModificationSignature": "202804489dea1be0a5952a0a0668d47e8996b586217d8ad06b5d35301a270f64"
"lastModificationSignature": "e589663e9d0f7b86bd955fa5f896c4baf8f4a5aac6054a89087b606e53e849e4"
}
}

View File

@ -3,7 +3,7 @@
"color": "#000000",
"deviceName": "S03_1_JR1",
"priority": "No Active Alarms",
"state": "Offline"
"state": "Actuated"
},
"params": {
"demoColor": -1,

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:03:48Z"
"timestamp": "2025-11-05T10:58:14Z"
},
"lastModificationSignature": "c0cad2fb0a077cee13c61f73d4e2698972da4e4f0919e1dc8186d33ccb57d204"
"lastModificationSignature": "0d465364c45191827e9b2d189ed92b7134de40f76d8b257abb57acb6ce25c269"
}
}

View File

@ -3,7 +3,7 @@
"color": "#000000",
"deviceName": "S03_CH115_EN",
"priority": "No Active Alarms",
"state": "Offline"
"state": "Closed"
},
"params": {
"demoColor": -1,

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-03T10:48:14Z"
"timestamp": "2025-11-06T09:45:29Z"
},
"lastModificationSignature": "e4adadbb2ccaa998f6711c2f1835a11716490fb19c8407ca298fb9f3f7f4ae71"
"lastModificationSignature": "a7f4a48b3b1044a614c495a7abbb5fc64daa1bd6fa748696d30ff0d6c61e4409"
}
}

View File

@ -3,16 +3,16 @@
"counts": {
"Critical": 0,
"Diagnostic": 0,
"High": 20,
"Low": 5,
"Medium": 2,
"Total": 27
"High": 6,
"Low": 2,
"Medium": 1,
"Total": 9
},
"totalAlarms": {
"$": [
"ds",
192,
1762166890980
1762422326706
],
"$columns": [
{
@ -39,11 +39,11 @@
},
{
"data": [
20,
5,
6,
2,
36,
3
1,
32,
4
],
"name": "Count",
"type": "Long"

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-03T10:48:13Z"
"timestamp": "2025-11-06T09:45:28Z"
},
"lastModificationSignature": "cf6029c178a1897c4e3745df0737345d13ee88bf22deb4eba98b83aba636794d"
"lastModificationSignature": "caef4a008da4d1c2473f73bb35520e0a7f394b4a25cb5947098e0dd928cf5dee"
}
}

View File

@ -4,7 +4,7 @@
"$": [
"ds",
192,
1762166893089
1762422327752
],
"$columns": [
{
@ -31,11 +31,11 @@
},
{
"data": [
20,
5,
6,
2,
36,
3
1,
32,
4
],
"name": "Count",
"type": "Long"

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-03T10:45:14Z"
"timestamp": "2025-11-06T09:28:00Z"
},
"lastModificationSignature": "905f499557e14f9a6ba14b18a8f3a8a99087be92b0ad57a907532cd840a2efea"
"lastModificationSignature": "33a2d01b5e1831e82442481afd4cfaa270b9e4136809c9c6711b8d1272c54167"
}
}

View File

@ -443,7 +443,7 @@
"basis": "32px"
},
"props": {
"text": 20,
"text": 6,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -472,7 +472,7 @@
"basis": "32px"
},
"props": {
"text": 2,
"text": 1,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -501,7 +501,7 @@
"basis": "32px"
},
"props": {
"text": 5,
"text": 2,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -559,7 +559,7 @@
"basis": "32px"
},
"props": {
"text": 27,
"text": 9,
"textStyle": {
"fontSize": 10,
"textAlign": "center"
@ -625,9 +625,9 @@
"counts": {
"Critical": 0,
"Diagnostic": 0,
"High": 20,
"Low": 5,
"Medium": 2
"High": 6,
"Low": 2,
"Medium": 1
}
},
"events": {

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:04:41Z"
"timestamp": "2025-11-06T05:53:32Z"
},
"lastModificationSignature": "c3053fd47ba6060f90c82b968094d2ad57a40bc60e1a63de3439b5bca745bda2"
"lastModificationSignature": "09a417e495e942ce0d88518e9bbbe1e80703111c87e5b1c709b329fb4aecb0a9"
}
}

View File

@ -571,7 +571,7 @@
"dom": {
"onClick": {
"config": {
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props)\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props, section \u003d \"conveyor\")\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
},
"scope": "G",
"type": "script"

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:05:09Z"
"timestamp": "2025-11-06T05:53:39Z"
},
"lastModificationSignature": "11f6963d22782647abcea15e4264ecba09836177042468b2d2cb434715c4ae1e"
"lastModificationSignature": "922086cbe69562d7e45d7d20f33be2d8f41991e47ae7e6a280195f0e92b4d23a"
}
}

View File

@ -594,7 +594,7 @@
"dom": {
"onClick": {
"config": {
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props)\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props, section \u003d \"conveyor\")\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
},
"scope": "G",
"type": "script"

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:05:46Z"
"timestamp": "2025-11-06T05:53:46Z"
},
"lastModificationSignature": "66e4286fd9730511928ddcf22341e9cf8d36aac0f861b6a32011597ee1e93490"
"lastModificationSignature": "41eb3b4d3418906754d43553be1845cdd7ccd9841a13e983b4f290d6e6247e8a"
}
}

View File

@ -574,7 +574,7 @@
"dom": {
"onClick": {
"config": {
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props)\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
"script": "\t#create devices and tags lists for the conveyor\n\tprops \u003d self.view.params.tagProps[0]\n\tautStand.devices.build_device_mapping(props)\n\tdevice_table_dataset \u003d autStand.devices.build_device_table(self)\n\ttags_table_dataset \u003d autStand.devices.getAllTags(self, props, section \u003d \"conveyor\")\n\tsystem.perspective.openDock(\u0027Docked-East-Conv\u0027,params\u003d{\u0027tagProps\u0027:self.view.params.tagProps, \"devices\": device_table_dataset, \"tags\":tags_table_dataset})"
},
"scope": "G",
"type": "script"

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T09:56:58Z"
"timestamp": "2025-11-05T16:03:12Z"
},
"lastModificationSignature": "47cb76182cad0ebbd030abdd3997bfc6c5031f7b3111d9378e0ec295e06995f3"
"lastModificationSignature": "af732729fae7c59783c445521081d73eb607e4723fe1070097c979d25a5dd1e1"
}
}

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T09:56:37Z"
"timestamp": "2025-11-05T11:04:37Z"
},
"lastModificationSignature": "73ef999335872207376b4932914dc91ce58ac2e266a384a16d2ed0679307a363"
"lastModificationSignature": "2967f9a672eda98f9b5149d0d6037ce4870a5c4458a77de30f302c5d94a0cb81"
}
}

View File

@ -4,7 +4,7 @@
"isHighlited": false,
"overlayColor": "#ffffff",
"priority": "No Active Alarms",
"state": "Offline"
"state": "BLOCKED"
},
"params": {
"demoColor": -1,

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T09:55:47Z"
"timestamp": "2025-11-05T11:02:12Z"
},
"lastModificationSignature": "e499dac6c15ef5769b713f487a901f89ddded7480c634bec50783348ccd686ad"
"lastModificationSignature": "e62e71b2c3c31d9b02ac9ae66a4e5a4858c85d9bb78bdc1beb5ca56be7eb8a90"
}
}

View File

@ -1,8 +1,8 @@
{
"custom": {
"color": "#000000",
"color": "#AAAAAA",
"deviceName": "S03_CH101_PRX1",
"state": "Offline"
"state": "INACTIVE"
},
"params": {
"demoColor": -1,

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T09:50:17Z"
"timestamp": "2025-11-05T10:58:14Z"
},
"lastModificationSignature": "00481db9366a0ae0a8ca6bea529f685857c34990822f5aa2f60b42c12400148b"
"lastModificationSignature": "e439249b233a0322cf68a4b7c22d5871ff1adf0d75c7b265d487f4f564ff9009"
}
}

View File

@ -4,7 +4,7 @@
"divertingLeft": false,
"divertingRight": false,
"priority": "No Active Alarms",
"state": "Offline"
"state": "Closed"
},
"params": {
"demoColor": 0,

View File

@ -0,0 +1,17 @@
{
"scope": "G",
"version": 1,
"restricted": false,
"overridable": true,
"files": [
"view.json",
"thumbnail.png"
],
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-06T10:41:41Z"
},
"lastModificationSignature": "94ada3882d5e031e352abc0f63fdb45b43f2491a46298cbcded7dcb4468f6367"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,77 @@
{
"custom": {},
"params": {
"text": "Provide for a sufficient cooling of the device."
},
"propConfig": {
"params.text": {
"paramDirection": "input",
"persistent": true
}
},
"props": {
"defaultSize": {
"height": 50,
"width": 300
}
},
"root": {
"children": [
{
"meta": {
"name": "Icon"
},
"position": {
"basis": "20px",
"shrink": 0
},
"props": {
"path": "material/brightness_1",
"style": {
"color": "black",
"marginLeft": 4,
"marginTop": 4
}
},
"type": "ia.display.icon"
},
{
"meta": {
"name": "Label"
},
"position": {
"grow": 1
},
"propConfig": {
"props.text": {
"binding": {
"config": {
"path": "view.params.text"
},
"type": "property"
}
}
},
"props": {
"textStyle": {
"color": "black",
"fontSize": "18px"
}
},
"type": "ia.display.label"
}
],
"meta": {
"name": "root"
},
"props": {
"alignContent": "center",
"alignItems": "flex-start",
"justify": "space-evenly",
"style": {
"gap": 7
}
},
"type": "ia.container.flex"
}
}

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T09:49:59Z"
"timestamp": "2025-11-06T09:34:59Z"
},
"lastModificationSignature": "eb4e651526b151add3fe79e7c2cc150331f570ba01f258f74f9960ce062abc28"
"lastModificationSignature": "a03070e098038bd2aa62a5692f809f523c8cdbc858d38157421e0a19292a0a79"
}
}

View File

@ -1,6 +1,6 @@
{
"custom": {
"color": "#000",
"color": "#f9050d",
"modifiedTag": "System/MCM01/VFD/UL14_1_VFD1",
"priority": "No Active Alarms"
},

View File

@ -10,8 +10,8 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-03T07:30:59Z"
"timestamp": "2025-11-06T09:10:53Z"
},
"lastModificationSignature": "6a0fecdc30b481e27569e24c81a59df066b326c50e14d901e82490fe84307fed"
"lastModificationSignature": "f3c965334dd56490910cdc0493c176ee8dd58074925b58c32317a373fa271897"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -3,7 +3,7 @@
"params": {},
"props": {
"defaultSize": {
"height": 1000
"height": 920
}
},
"root": {

View File

@ -1,308 +1,542 @@
import os, json, sys
# ======================================================
# Helper Function: State Resolver
# ======================================================
def get_device_state(value, tagPath):
up = tagPath.upper()
# === Base state dictionary ===
state_mappings = {
0: "Closed",
1: "Actuated",
2: "Communication Faulted",
3: "Conveyor Running In Maintenance Mode",
4: "Disabled",
5: "Disconnected",
6: "Stopped",
7: "Enabled Not Running",
8: "Encoder Fault",
9: "Energy Management",
10: "ESTOP Was Actuated",
11: "EStopped",
12: "EStopped Locally",
13: "Extended Faulted",
14: "Full",
15: "Gaylord Start Pressed",
16: "Jam Fault",
17: "Jammed",
18: "Loading Allowed",
19: "Loading Not Allowed",
20: "Low Air Pressure Fault Was Present",
21: "Maintenance Mode",
22: "Conveyor Stopped In Maintenance Mode",
23: "Motor Faulted",
24: "Motor Was Faulted",
25: "Normal",
26: "Off Inactive",
27: "Open",
28: "PLC Ready To Run",
29: "Package Release Pressed",
30: "Power Branch Was Faulted",
31: "Pressed",
32: "Ready To Receive",
33: "Running",
34: "Started",
35: "Stopped",
36: "System Started",
37: "Unknown",
38: "VFD Fault",
39: "Conveyor Running In Power Saving Mode",
40: "Conveyor Jogging In Maintenance Mode",
41: "VFD Reset Required",
42: "Jam Reset Push Button Pressed",
43: "Start Push Button Pressed",
44: "Stop Push Button Pressed",
45: "No Container",
46: "Ready To Be Enabled",
47: "Half Full",
48: "Enabled",
49: "Tipper Faulted",
50: "OK",
51: "Disconnected",
52: "Faulted",
53: "Faulted/Disconnect",
54: "Diverting"
}
# === TPE (Tracking Photoeye) ===
if "/TPE/" in up:
if value == 0:
return "Blocked"
elif value == 27:
return "Clear"
elif value == 17:
return "Jammed"
else:
return state_mappings.get(value, "Unknown")
# === Single Photoeyes (PE1, PE2) ===
if up.endswith(("PE1", "PE2")):
if not value:
return "Clear"
else:
return "Blocked"
# === Prox Sensors (PRX1, PRX2) ===
if up.endswith(("PRX1", "PRX2")):
if not value:
return "Inactive"
else:
return "Actuated"
# === Beacons (BCN) ===
if "/BEACON" in up:
if value == 0:
return "Off"
elif value == 1:
return "Cleared / Reset Required"
else:
return "Active"
# === Default ===
return state_mappings.get(value, "Unknown")
# ======================================================
# Helper Function: Read One Device (multi or single state)
# ======================================================
def read_device_status(tagPath, provider, dev_name):
"""
Reads the appropriate state tag(s) for a given device and returns
a list of {Device, Status} dictionaries.
Handles multi-state (SS), VFD, PRX, PE, EN, etc.
"""
rows = []
try:
up = tagPath.upper()
# === Case 0: SS (Start/Stop Station) ===
if up.endswith("SS") or "/SS/" in up:
for sub in ("Start", "Stop"):
sub_path = provider + tagPath + "/" + sub + "/State"
try:
result = system.tag.readBlocking([sub_path])[0]
if result.quality.isGood():
status_value = get_device_state(result.value, tagPath)
else:
status_value = "Unknown"
except:
status_value = "Unknown"
rows.append({
"Device": "{} ({})".format(dev_name, sub),
"Status": status_value
})
return rows # handled fully
# === Case 1: VFD / Conveyor ===
if "/VFD/" in up:
path = provider + tagPath + "/Drive/Lenze"
# === Case 2: Chute sensors (PE / PRX) ===
elif up.endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in up:
path = provider + tagPath
# === Case 3: Chute EN ===
elif up.endswith("EN") and "/CHUTE/" in up:
path = provider + tagPath + "_State"
# === Case 4: Default ===
else:
path = provider + tagPath + "/State"
try:
result = system.tag.readBlocking([path])[0]
if result.quality.isGood():
status_value = get_device_state(result.value, tagPath)
else:
status_value = "Offline"
except:
status_value = "Offline"
rows.append({
"Device": dev_name,
"Status": status_value
})
except Exception as e:
system.perspective.print("Error reading device status for %s: %s" % (dev_name, e))
return rows
# ======================================================
# Helper Function: Single Device Reader (for Docked Device View)
# ======================================================
def get_single_device_status(self, tagPath):
"""
Reads a single device tag (used for docked device views).
Returns a single readable status string (e.g. "Running", "Blocked", etc.)
"""
try:
up = tagPath.upper()
provider = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
if up.endswith("SS") or "/SS/" in up:
states = []
for sub in ("Start", "Stop"):
sub_path = provider + tagPath + "/" + sub + "/State"
try:
result = system.tag.readBlocking([sub_path])[0]
if result.quality.isGood():
states.append("{}: {}".format(sub, get_device_state(result.value, tagPath)))
else:
states.append("{}: Unknown".format(sub))
except:
states.append("{}: Unknown".format(sub))
return " | ".join(states)
# === VFD ===
if "/VFD/" in up:
path = provider + tagPath + "/Drive/Lenze"
# === Sensors ===
elif up.endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in up:
path = provider + tagPath
# === EN ===
elif up.endswith("EN") and "/CHUTE/" in up:
path = provider + tagPath + "_State"
else:
path = provider + tagPath + "/State"
result = system.tag.readBlocking([path])[0]
if result.quality.isGood():
return get_device_state(result.value, tagPath)
else:
return "Unknown"
except Exception as e:
system.perspective.print("Error reading single device status for %s: %s" % (tagPath, e))
return "Unknown"
# ======================================================
# Device Mapping Builder
# ======================================================
global_device_mapping = {}
def build_device_mapping(full_tag_path):
"""
Builds global_device_mapping for devices that:
- Belong to the same PLC (index 1)
- Are children of the clicked device (start with clicked_name + "_")
"""
global global_device_mapping
global_device_mapping.clear()
"""
Builds global_device_mapping for devices under the same PLC and parent device.
Adds support for:
- Chute FIOM devices (e.g. S03_CH109_FIOM_1 when clicking S03_CH109)
- Shared JR and PE devices used by multiple chutes (e.g. S03_1_JR1, S03_1_LRPE1)
"""
system.perspective.print(full_tag_path)
global global_device_mapping
global_device_mapping.clear()
try:
# Parse PLC and clicked device
path_parts = full_tag_path.split("/")
plc_name = path_parts[1] if len(path_parts) > 1 else path_parts[0]
clicked_name = path_parts[-1] if len(path_parts) > 0 else ""
if "_VFD" in clicked_name:
idx = clicked_name.find("_VFD")
if idx != -1:
clicked_name = clicked_name[:idx]
try:
path_parts = full_tag_path.split("/")
plc_name = path_parts[1] if len(path_parts) > 1 else path_parts[0]
clicked_name = path_parts[-1] if len(path_parts) > 0 else ""
project_name = system.util.getProjectName()
base_path = (
os.getcwd().replace("\\", "/")
+ "/data/projects/"
+ project_name
+ "/com.inductiveautomation.perspective/Views/autStand/Detailed_Views/MCM-Views"
)
# --- Clean clicked name ---
if "_VFD" in clicked_name:
clicked_name = clicked_name.split("_VFD")[0]
if not os.path.exists(base_path):
system.perspective.print("Path not found: " + base_path)
return {}
project_name = system.util.getProjectName()
base_path = (
os.getcwd().replace("\\", "/")
+ "/data/projects/"
+ project_name
+ "/com.inductiveautomation.perspective/Views/autStand/Detailed_Views/MCM-Views"
)
# loop through all view folders
for view_folder in os.listdir(base_path):
json_file = os.path.join(base_path, view_folder, "view.json")
if not os.path.isfile(json_file):
continue
if not os.path.exists(base_path):
system.perspective.print("Path not found: " + base_path)
return {}
try:
with open(json_file, "r") as fh:
view_json = json.load(fh)
except Exception:
continue
# --- Detect if this is a Chute ---
is_chute = "/CHUTE/" in full_tag_path.upper()
# go one level deeper: root -> children[0] (coordinateContainer) -> its children
root_children = (view_json.get("root") or {}).get("children") or []
if not root_children:
continue
for view_folder in os.listdir(base_path):
json_file = os.path.join(base_path, view_folder, "view.json")
if not os.path.isfile(json_file):
continue
container = root_children[0]
children = container.get("children") or []
try:
with open(json_file, "r") as fh:
view_json = json.load(fh)
except Exception:
continue
for child in children:
props = child.get("props") or {}
params = props.get("params") or {}
tag_props = params.get("tagProps")
root_children = (view_json.get("root") or {}).get("children") or []
if not root_children:
continue
if isinstance(tag_props, list) and len(tag_props) > 0:
tag_prop = str(tag_props[0])
parts = tag_prop.split("/")
container = root_children[0]
children = container.get("children") or []
if len(parts) > 1 and parts[1] == plc_name:
dev_name = parts[-1]
if len(parts) > 3 and parts[-2] == clicked_name:
dev_name = clicked_name + "_" + parts[-1]
system.perspective.print(dev_name)
for child in children:
props = child.get("props") or {}
params = props.get("params") or {}
tag_props = params.get("tagProps")
# ONLY include devices that are children of clicked_name
else:
dev_name = parts[-1]
prefix = clicked_name + "_"
if dev_name.startswith(prefix) or (len(parts) > 3 and parts[-2] == clicked_name):
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
return global_device_mapping
if isinstance(tag_props, list) and len(tag_props) > 0:
tag_prop = str(tag_props[0])
parts = tag_prop.split("/")
except Exception as e:
whid = "unknown"
try:
whid = system.tag.readBlocking("Configuration/FC")[0].value
except:
pass
logger = system.util.getLogger("%s-build_device_mapping" % whid)
exc_type, exc_obj, tb = sys.exc_info()
logger.error("Error at line %s: %s" % (tb.tb_lineno, exc_obj))
return {}
if len(parts) > 1 and parts[1] == plc_name:
dev_name = parts[-1]
if len(parts) > 3 and parts[-2] == clicked_name:
dev_name = clicked_name + "_" + parts[-1]
else:
dev_name = parts[-1]
prefix = clicked_name + "_"
# === 🟢 NEW: Chute FIOM match ===
if is_chute and dev_name.startswith(clicked_name + "_"):
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
continue
# === Default inclusion ===
if dev_name.startswith(prefix) or (len(parts) > 3 and parts[-2] == clicked_name):
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
# === Special Case: JR Buttons ===
elif "/JR/" in tag_prop.upper():
try:
jr_parts = tag_prop.split("/JR/")
if len(jr_parts) > 1:
sub_path = jr_parts[1]
if sub_path.startswith(clicked_name + "_JR"):
dev_name = sub_path.split("/")[0]
global_device_mapping[dev_name] = {
"tagPath": tag_prop,
"zone": view_folder
}
except:
pass
shared_jr_pe_map = {
"S03_CH101": ["S03_1_JR1", "S03_1_LRPE1"],
"S03_CH103": ["S03_1_JR1", "S03_1_LRPE1"],
"S03_CH105": ["S03_1_JR1", "S03_1_LRPE1"],
"S03_CH107": ["S03_1_JR3", "S03_1_LRPE3"],
"S03_CH108": ["S03_1_JR4", "S03_1_LRPE4"],
"S03_CH109": ["S03_1_JR3", "S03_1_LRPE3"],
"S03_CH110": ["S03_1_JR4", "S03_1_LRPE4"],
"S03_CH111": ["S03_1_JR3", "S03_1_LRPE3"],
"S03_CH112": ["S03_1_JR2", "S03_1_LRPE2"],
"S03_CH113": ["S03_1_JR5", "S03_1_LRPE5"],
"S03_CH114": ["S03_1_JR6", "S03_1_LRPE6"],
"S03_CH115": ["S03_1_JR5", "S03_1_LRPE5"],
"S03_CH116": ["S03_1_JR6", "S03_1_LRPE6"],
"S03_CH117": ["S03_1_JR5", "S03_1_LRPE5"],
"S03_CH118": ["S03_1_JR6", "S03_1_LRPE6"],
"S03_CH119": ["S03_1_JR7", "S03_1_LRPE7"],
"S03_CH120": ["S03_1_JR8", "S03_1_LRPE8"],
"S03_CH121": ["S03_1_JR7", "S03_1_LRPE7"],
"S03_CH122": ["S03_1_JR8", "S03_1_LRPE8"],
"S03_CH123": ["S03_1_JR7", "S03_1_LRPE7"],
"S03_CH124": ["S03_1_JR8", "S03_1_LRPE8"],
}
shared_fiom_map = {
"NCS1_1": ["S03_1_FIOM_5", "S03_1_FIOM_9", "S03_1_FIOM_1", "S03_1_FIOM_2","S03_1_FIOM_3","S03_1_FIOM_4", "S03_1_FIOM_6","S03_1_FIOM_7", "S03_1_FIOM_8"],
}
if clicked_name in shared_jr_pe_map:
extra_devices = shared_jr_pe_map[clicked_name]
for dev in extra_devices:
try:
# Base tag (for PE)
base_tag = "System/MCM02/Station/Chute_JR/" + dev
# JR subtag (for JR button)
jr_tag = base_tag + "/JR" if dev.endswith("JR1") else base_tag
for tag_candidate in [base_tag, jr_tag]:
global_device_mapping[dev] = {
"tagPath": tag_candidate,
"zone": "Chute_JR"
}
except Exception as ex:
system.perspective.print("Error adding JR/PE for {}: {}".format(clicked_name, ex))
if clicked_name in shared_fiom_map:
for dev in shared_fiom_map[clicked_name]:
tag_path = "System/{}/IO_Block/FIO/{}".format(plc_name, dev)
global_device_mapping[dev] = {
"tagPath": tag_path,
"zone": "FIO"
}
return global_device_mapping
except Exception as e:
whid = "unknown"
try:
whid = system.tag.readBlocking("Configuration/FC")[0].value
except:
pass
logger = system.util.getLogger("%s-build_device_mapping" % whid)
exc_type, exc_obj, tb = sys.exc_info()
logger.error("Error at line %s: %s" % (tb.tb_lineno, exc_obj))
return {}
# ======================================================
# Device Table Builder
# ======================================================
def build_device_table(self):
"""
Converts global_device_mapping into a list of dictionaries:
Keys: Device, Status
Reads each tag value, falls back to 'Unknown' if error/null.
"""
rows = []
state_mappings = {
0: "Closed",
1: "Actuated",
2: "Communication Faulted",
3: "Conveyor Running In Maintenance Mode",
4: "Disabled",
5: "Disconnected",
6: "Stopped",
7: "Enabled Not Running",
8: "Encoder Fault",
9: "Energy Management",
10: "ESTOP Was Actuated",
11: "EStopped",
12: "EStopped Locally",
13: "Extended Faulted",
14: "Full",
15: "Gaylord Start Pressed",
16: "Jam Fault",
17: "Jammed",
18: "Loading Allowed",
19: "Loading Not Allowed",
20: "Low Air Pressure Fault Was Present",
21: "Maintenance Mode",
22: "Conveyor Stopped In Maintenance Mode",
23: "Motor Faulted",
24: "Motor Was Faulted",
25: "Normal",
26: "Off Inactive",
27: "Open",
28: "PLC Ready To Run",
29: "Package Release Pressed",
30: "Power Branch Was Faulted",
31: "Pressed",
32: "Ready To Receive",
33: "Running",
34: "Started",
35: "Stopped",
36: "System Started",
37: "Unknown",
38: "VFD Fault",
39: "Conveyor Running In Power Saving Mode",
40: "Conveyor Jogging In Maintenance Mode",
41: "VFD Reset Required",
42: "Jam Reset Push Button Pressed",
43: "Start Push Button Pressed",
44: "Stop Push Button Pressed",
45: "No Container",
46: "Ready To Be Enabled",
47: "Half Full",
48: "Enabled",
49: "Tipper Faulted"
}
rows = []
try:
for dev_name, info in global_device_mapping.items():
tagPath = info.get("tagPath", "")
if not tagPath:
continue
try:
for dev_name, info in global_device_mapping.items():
tagPath = info.get("tagPath", "")
status_value = ""
provider = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
path = provider + tagPath + "/STATE"
if tagPath:
try:
result = system.tag.readBlocking([path])[0]
status_value = state_mappings.get(result.value, "Unknown")
except:
status_value = "Unknown"
provider = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
rows.extend(read_device_status(tagPath, provider, dev_name))
# Append as dictionary
rows.append({
'Device': dev_name,
'Status': status_value
})
return rows
except Exception as e:
system.perspective.print("Error building device table: %s" % e)
return [] # Return empty list on error
return rows
except Exception as e:
system.perspective.print("Error building device table: %s" % e)
return []
# ======================================================
# Get All Tags for Clicked Device
# ======================================================
def getAllTags(self, tagPath, section="all"):
"""
Reads all tags under a UDT instance (recursively) and returns a list of dictionaries.
"""
Reads all tags under a UDT instance (recursively)
and returns a list of dictionaries:
[
{"Name": "State", "OPC Path": "System/MCM01/...", "Value": "Running"},
...
]
"""
rows = []
Supports:
- VFD (Drive folder)
- Conveyor (skips Drive)
- Chute (root + PE/PRX/EN tags)
- Single Photoeyes (PE1/PE2)
- Single Prox Sensors (PRX1/PRX2)
- Enable buttons (EN_Color, EN_State, EN_Priority)
- Tracking Photoeyes (TPE, handles both folder- and struct-style UDTs)
"""
try:
providerPath = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
driveFolderName = "Drive"
rows = []
# === Utility: read a single atomic tag ===
def readSingleTag(path, prefix=""):
try:
result = system.tag.readBlocking([providerPath + path])[0]
value = str(result.value) if result.quality.isGood() else "Unknown"
except:
value = "Unknown"
try:
providerPath = "[" + self.session.custom.fc + "_SCADA_TAG_PROVIDER]"
driveFolderName = "Drive"
displayName = prefix + path.split("/")[-1] if prefix else path.split("/")[-1]
rows.append({
"Name": displayName,
"OPC Path": path,
"Value": value
})
# === Utility: recursive browse ===
def browseRecursive(basePath, prefix=""):
children = system.tag.browse(providerPath + basePath).getResults()
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
fullPath = str(child.get("fullPath", ""))
# === Utility: read a single atomic tag ===
def readSingleTag(path, prefix=""):
try:
result = system.tag.readBlocking([providerPath + path])[0]
value = str(result.value) if result.quality.isGood() else "Unknown"
except:
value = "Unknown"
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
displayName = prefix + path.split("/")[-1] if prefix else path.split("/")[-1]
rows.append({
"Name": displayName,
"OPC Path": path,
"Value": value
})
# --- Conveyor filter (skip Drive folder) ---
if section == "conveyor" and name == driveFolderName:
continue
# === Utility: recursive browse ===
def browseRecursive(basePath, prefix=""):
children = system.tag.browse(providerPath + basePath).getResults()
if tagType == "Folder":
# --- Skip JR subfolder if current device is LRPE ---
if name.upper() == "JR" and "_JR" in basePath.upper():
continue
newPrefix = prefix + name + "/" if prefix else name + "/"
browseRecursive(basePath + "/" + name, newPrefix)
elif tagType == "AtomicTag":
readSingleTag(fullPath, prefix)
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
fullPath = str(child.get("fullPath", ""))
# === MAIN ENTRY POINT ===
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
# --- Case 1: VFD ---
if section == "vfd":
drivePath = tagPath + "/" + driveFolderName
browseRecursive(drivePath)
# --- Conveyor filter (skip Drive folder) ---
if section == "conveyor" and name == driveFolderName:
continue
# --- Case 2: Flat EN_ tags (Chutes) ---
elif tagPath.upper().endswith("/EN"):
parentPath = "/".join(tagPath.split("/")[:-1])
children = system.tag.browse(providerPath + parentPath).getResults()
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
if tagType == "AtomicTag" and name.upper().startswith("EN_"):
fullPath = str(child.get("fullPath", ""))
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
readSingleTag(fullPath)
if tagType == "Folder":
newPrefix = prefix + name + "/" if prefix else name + "/"
browseRecursive(basePath + "/" + name, newPrefix)
elif tagType == "AtomicTag":
readSingleTag(fullPath, prefix)
# --- Case 3: Single Sensors (PE/PRX) ---
elif tagPath.upper().endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in tagPath.upper():
readSingleTag(tagPath)
# === MAIN ENTRY POINT ===
if section == "vfd":
# Browse only inside Drive folder
drivePath = tagPath + "/" + driveFolderName
browseRecursive(drivePath)
# --- Case 4: Default / Fallback ---
else:
browseResult = system.tag.browse(providerPath + tagPath).getResults()
elif tagPath.upper().endswith("/EN"):
# --- Handle flat EN_ tags ---
parentPath = "/".join(tagPath.split("/")[:-1])
children = system.tag.browse(providerPath + parentPath).getResults()
if not browseResult:
# Possibly a struct-style UDT (like some TPEs)
system.perspective.print("Empty browse for {}, checking struct value...".format(tagPath))
try:
result = system.tag.readBlocking([providerPath + tagPath])[0]
value = result.value
for child in children:
tagType = str(child.get("tagType", ""))
name = str(child.get("name", ""))
if tagType == "AtomicTag" and name.upper().startswith("EN_"):
fullPath = str(child.get("fullPath", ""))
if fullPath.startswith("[") and "]" in fullPath:
fullPath = fullPath.split("]", 1)[1]
readSingleTag(fullPath)
# === Expand STRUCT ===
if isinstance(value, dict):
system.perspective.print("Detected STRUCT value, expanding {}".format(tagPath))
elif tagPath.upper().endswith(("PE1", "PE2", "PRX1", "PRX2")) and "/TPE/" not in tagPath.upper():
# --- Single sensors ---
readSingleTag(tagPath)
def flattenStruct(struct, base=""):
for k, v in struct.items():
newName = base + "/" + k if base else k
if isinstance(v, dict):
flattenStruct(v, newName)
else:
rows.append({
"Name": newName,
"OPC Path": tagPath + "/" + newName,
"Value": str(v)
})
else:
# --- Default path ---
browseResult = system.tag.browse(providerPath + tagPath).getResults()
flattenStruct(value)
if not browseResult:
# Possibly a struct-style UDT (like some TPEs)
system.perspective.print("Empty browse for {}, checking struct value...".format(tagPath))
try:
result = system.tag.readBlocking([providerPath + tagPath])[0]
value = result.value
else:
# Not a struct, read normally
readSingleTag(tagPath)
# If we got a STRUCT, expand it into sub-rows
if isinstance(value, dict):
system.perspective.print("Detected STRUCT value, expanding {}".format(tagPath))
except Exception as ex:
system.perspective.print("Fallback read failed for {}: {}".format(tagPath, ex))
def flattenStruct(struct, base=""):
for k, v in struct.items():
newName = base + "/" + k if base else k
if isinstance(v, dict):
flattenStruct(v, newName)
else:
rows.append({
"Name": newName,
"OPC Path": tagPath + "/" + newName,
"Value": str(v)
})
else:
# Normal browse case
browseRecursive(tagPath)
flattenStruct(value)
else:
# Not a struct, just read it normally
readSingleTag(tagPath)
except Exception as ex:
system.perspective.print("Fallback read failed for {}: {}".format(tagPath, ex))
else:
# Normal case — browse folder/UDT structure
browseRecursive(tagPath)
return rows
return rows
except Exception as e:
system.perspective.print("Error in getAllTags: {}".format(e))
return []
except Exception as e:
system.perspective.print("Error in getAllTags: {}".format(e))
return []

View File

@ -9,9 +9,9 @@
"attributes": {
"lastModification": {
"actor": "admin",
"timestamp": "2025-11-05T10:10:15Z"
"timestamp": "2025-11-06T09:07:05Z"
},
"hintScope": 2,
"lastModificationSignature": "509aec4ea42ad044944f9f2f53d5e610bd5fdb0ed852c6d392f7a94816fa6cd9"
"lastModificationSignature": "1aab25cf2caca967d0c6fc4a2cbc161a07f54ef8faf7c2675b94d53bb2e289b9"
}
}