Files
Stream_PHX_A7/Gateways/FE/projects/.resources/a9b336db7c61e8e80ab9c06ec167d5fdf7718f90949f31fda86e4950cbb05604

385 lines
15 KiB
Plaintext

from datetime import datetime
def applyExclusions(items, datasetTagpath):
"""
Sorts a list of tag dictionary objects.
This function assumes that the UDT structure has a '{rootPath}/Manual Exclusions/{Info|Config|Meta}'. We fail gracefully if not.
Args:
items (List[Dict]): A list of tag dictionaries with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
datasetTagpath (String): A tagpath to an L4 UDT Category, i.e "[PHXA2_IG_COMN]System/Device Datasets/RTU/Info".
Returns:
List[Dict]: A device specific dataset with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
"""
# parse datasetTagpath
rootPath = datasetTagpath[:datasetTagpath.rindex('/')] # "[PHXA2_IG_COMN]System/Device Datasets/RTU"
category = datasetTagpath[datasetTagpath.rindex('/')+1:] # "Info"
exclusionPath = '{rootPath}/Manual Exclusions/{category}'.format(rootPath=rootPath, category=category)
logger = system.util.getLogger("Device Dataset/itemsExcluded()")
res = []
# Read sort order, convert from unicode to string, and return unchanged items if empty.
data = system.tag.readBlocking(exclusionPath)[0]
excludedTags = list(data.value) if (data and data.value is not None) else []
excludedTags = [str(tag) for tag in excludedTags]
if len(excludedTags) == 0: return items
return list(filter(lambda x: str(x.get('tagName')) not in excludedTags, items))
def getTagInstances(deviceDataset, tagPath, folder=""):
"""
Iterates through a DeviceDataset and returns instances for a Tag Info FlexRepeater.
This function is called by the 'instances' binding in the L4 /Info views.
Args:
deviceDataset (Document): A device specific dataset with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
These are stored in [PHXA2_IG_COMN]System/Device Datasets.
tagPath (String): A tagpath to the root of a device, i.e "[PHXA2_IG_COMN]RM2505/PHXA2_RM2505_FCU_4".
folder (String): The folder the tags live in, i.e "/Config" or "/Meta".
Returns:
List[List]: instances to be used in a /Tag Info FlexRepeater.
"""
# Append /Config or /Meta to the tagpath.
tagPath += folder
res = []
for item in list(deviceDataset):
instance = {
"name": item.get("tagName", "N/A"),
"dataType": item.get("engUnit", "N/A"),
"tagPath": item.get("tagPath", ""),
"isUDT": item.get("isUDT", False),
"udtType": item.get("udtType", "")
}
res.append(instance)
return res
def getSortedTagDictionaries(items, datasetTagpath):
"""
Sorts a list of tag dictionary objects.
This function assumes that the UDT structure has a '{rootPath}/Configuration/{Info|Config|Meta}SortOrder'. We fail gracefully if not.
Args:
items (List[Dict]): A list of tag dictionaries with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
datasetTagpath (String): A tagpath to an L4 UDT Category, i.e "[PHXA2_IG_COMN]System/Device Datasets/RTU/Info".
Returns:
List[Dict]: A device specific dataset with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
"""
# parse datasetTagpath
rootPath = datasetTagpath[:datasetTagpath.rindex('/')] # "[PHXA2_IG_COMN]System/Device Datasets/RTU"
category = datasetTagpath[datasetTagpath.rindex('/')+1:] # "Info"
sortOrderPath = '{rootPath}/Sorting Orders/{category}'.format(rootPath=rootPath, category=category)
# Read sort order, convert from unicode to string, and return unchanged items if empty.
data = system.tag.readBlocking(sortOrderPath)[0]
sortingOrder = list(data.value) if (data and data.value is not None) else []
sortingOrder = [str(item) for item in sortingOrder]
if len(sortingOrder) == 0: return items
# Sort items based on tag names in sorting order.
sortedItems = sorted(
items,
key=lambda x: (
sortingOrder.index(x['tagName']) if x['tagName'] in sortingOrder else len(sortingOrder) # append unsorted tags
)
)
return sortedItems
def getDiff(previousData, currentData, folder=""):
"""
Get difference between data before generation and after. This is meant to track
manual additions being lost (functionality which will be improved soon^tm).
Args:
previousData (List[Dict]): A device specific dataset with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
currentData (List[Dict]): A device specific dataset with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
Returns:
String: A timestamped log message detailing tags that existed in previousData and not in currentData.
"""
logMessage = ""
for item in previousInfo:
tagName = item.get("tagName", "")
if tagName is "":
continue
print(
"{} in {}? {}.".format(tagName, currentData, tagName in currentData)
)
logger.info(
"{} in {}? {}.".format(tagName, currentData, tagName in currentData)
)
if tagName not in currentData:
logMessage += "\n{}: {} was removed in {} update.".format(timestamp, folder, item.tagName)
return logMessage
def generateDeviceDataset(deviceTagpath, datasetTagpath, writeToSystem=True, debug=False):
"""
Create a new device dataset.
Args:
deviceTagpath (String): A tagpath to the root of a device, i.e "[PHXA2_IG_COMN]Roof/PHXA2_ROOF_RTU_1".
datasetTagpath (String): A tagpath to an L4 UDT category, i.e "[PHXA2_IG_COMN]System/Device Datasets/RTU/{Info|Config|Meta|Alarms}".
writeToSystem (Boolean)[Optional]: If specified as false, returns the result instead of writing it
to datasetTagpath in the system.
Returns:
List[Dict]: A device specific dataset with the structure: [{tagName: 'Hello World', 'engUnit': 'String'}, ...].
"""
import json
allTagsOutput = []
#deviceName = deviceTagpath[deviceTagpath.index('/')+1:]
if '/' in deviceTagpath:
deviceName = deviceTagpath[deviceTagpath.rindex('/')+1:]
else:
deviceName = deviceTagpath
logger = system.util.getLogger('{} Dataset'.format(deviceName))
# Fetch tag configurations and sort by name
nodes = system.tag.getConfiguration(deviceTagpath, True)
for item in nodes:
if "tags" in item:
sortedTags = sorted(item["tags"], key=lambda x: x["name"])
sortedTags = sorted(
sortedTags,
key=lambda x: (
x.get("engUnit", "misc")
if x.get("engUnit", "misc") != ""
else "misc"
),
)
for tagConfig in sortedTags:
name = tagConfig.get("name", "")
t_type = str(tagConfig.get("tagType", ""))
fullPath = "{}/{}".format(deviceTagpath, name)
instance = {
"instanceStyle": {"classes": ""},
"instancePosition": {},
"name": name,
"tagName": name,
"tagPath": fullPath
}
if t_type == "AtomicTag":
instance["dataType"] = tagConfig.get("dataType", "")
instance["isUDT"] = False
# Group based on name containing 'Power', 'Current', or 'Voltage'
allTagsOutput.append(instance)
elif t_type =="UdtInstance":
exemptions = ['AlarmSummary', 'Meta', 'Maintenance']
if name not in exemptions:
instance['dataType'] = 'UDT'
instance['udtType'] = tagConfig.get('typeId', 'Unknown UDT')
instance['isUDT'] = True
allTagsOutput.append(instance)
if debug:
logger.info('allTagsOutput: {}'.format(allTagsOutput))
print('allTagsOutput: {}'.format(allTagsOutput))
### FlexRepeater Binding
bindingOutput = []
for tag in allTagsOutput:
tagName = tag["name"]
tagPath = tag['tagPath']
tag["tagPath"] = tagPath + "/" + tagName
bindingOutput.append(tag)
### Logan Script
res = []
for item in bindingOutput:
entry = item.copy()
entry['tagName'] = item.get('name','')
entry['engUnit'] = item.get('dataType','')
#tagPath = item["tagPath"]
#tagNameIndex = tagPath.rindex("/") + 1
#res.append({"tagName": tagPath[tagNameIndex:], "engUnit": item["dataType"]})
res.append(entry)
### Excluding
excludedTagDictionaries = applyExclusions(items=res, datasetTagpath=datasetTagpath)
### Sorting
sortedTagDictionaries = getSortedTagDictionaries(items=excludedTagDictionaries, datasetTagpath=datasetTagpath)
if debug:
logger.info('sortedTagDictionaries: {}'.format(sortedTagDictionaries))
logger.info('writing to: {}'.format(datasetTagpath))
print('sortedTagDictionaries: {}'.format(sortedTagDictionaries))
print('writing to: {}'.format(datasetTagpath))
if writeToSystem:
system.tag.writeBlocking(datasetTagpath, [sortedTagDictionaries])
return sortedTagDictionaries
def generateDeviceDatasetTrends(deviceTagpath, datasetTagpath, writeToSystem=True):
return {}
def generateL4(datasetTagpath, writeToSystem=True, debug=False):
"""
Highest level function. Can be called by users.
Generate Info, Config, Meta, and Trends in a L4 UDT. Requires a UDT to exist with a reference tagpath specified.
This function is called by _Manual Refresh in the L4 UDT.
Args:
datasetTagpath (String): A tagpath to an L4 UDT, i.e "[PHXA2_IG_COMN]System/Device Datasets/RTU".
writeToSystem (Boolean)[Optional]: If specified as false, returns the result instead of writing it
to datasetTagpath in the system.
debug (Boolean)[Optional]: Enable verbose logging.
Returns:
Dict{key: val}: A dictionary containing each dataset. Keys are ['info','config','meta','trends'].
"""
timestamp = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]") # Returns "[2025-08-08 08:28:32]".
logger = system.util.getLogger("Device Dataset")
logMessage = ""
if debug:
logger.info("Generating L4 for {}.".format(datasetTagpath))
print(datasetTagpath)
# Get all relevant UDT data.
data = system.tag.readBlocking(
[
datasetTagpath + "/Info",
datasetTagpath + "/Config",
datasetTagpath + "/Meta",
datasetTagpath + "/Trends",
datasetTagpath + "/Alarms",
datasetTagpath + "/_Reference Tagpath",
]
)
# Catch bad tagPaths and non-existent data.
previousInfo = data[0].value if (data and data[0].value is not None) else {}
previousConfig = data[1].value if (data and data[1].value is not None) else {}
previousMeta = data[2].value if (data and data[2].value is not None) else {}
previousTrends = data[3].value if (data and data[3].value is not None) else {}
previousAlarms = data[4].value if (data and data[4].value is not None) else {}
referenceTagpath = data[5].value if (data and data[5].value is not None) else {}
# If no reference device is given, the script explodes. We catch it here.
if referenceTagpath is None or len(referenceTagpath) == 0:
logger.info(
"\n{}: ERROR: Please verify that a reference tagpath is provided.".format(timestamp)
)
logMessage += (
"\n{}: ERROR: Please verify that a reference tagpath is provided.".format(timestamp)
)
system.tag.writeAsync([datasetTagpath + "/_Log Message"], [logMessage])
return # exit early
if debug:
logger.info(
"prevInfo: {}\nprevConfig: {}\n prevTrends: {}\n prevMeta: {}\n refTag: {}".format(
previousInfo,
previousConfig,
previousTrends,
previousMeta,
referenceTagpath,
)
)
print(
"prevInfo: {}\nprevConfig: {}\n prevTrends: {}\n prevMeta: {}\n refTag: {}".format(
previousInfo,
previousConfig,
previousTrends,
previousMeta,
referenceTagpath,
)
)
# Build the dataset for the Info view. Pull each tagName from the dataset to track which manual additions are lost.
currentInfo = Global.DeviceDatasets.generateDeviceDataset(
deviceTagpath="{referenceTagpath}".format(referenceTagpath=referenceTagpath), # DON'T APPEND THIS WITH ANYTHING
datasetTagpath="{datasetTagpath}/Info".format(datasetTagpath=datasetTagpath),
writeToSystem=writeToSystem,
)
# Note manual additions that are lost in the update.
if debug:
currentInfoNames = list(map(lambda x: str(x.get("tagName", "N/A")), currentInfo))
logMessage += Global.DeviceDatasets.getDiff(previousData=previousInfo, currentData=currentInfo, folder="Info")
currentConfig = Global.DeviceDatasets.generateDeviceDataset(
deviceTagpath="{referenceTagpath}/Config".format(referenceTagpath=referenceTagpath),
datasetTagpath="{datasetTagpath}/Config".format(datasetTagpath=datasetTagpath),
writeToSystem=writeToSystem,
)
if debug:
currentConfigNames = list(map(lambda x: str(x.get("tagName", "N/A")), currentConfig))
logMessage += Global.DeviceDatasets.getDiff(previousData=previousConfig, currentData=currentConfigNames, folder="Config")
currentMeta = Global.DeviceDatasets.generateDeviceDataset(
deviceTagpath="{referenceTagpath}/Meta".format(referenceTagpath=referenceTagpath),
datasetTagpath="{datasetTagpath}/Meta".format(datasetTagpath=datasetTagpath),
writeToSystem=True,
)
if debug:
currentMetaNames = list(map(lambda x: str(x.get("tagName", "N/A")), currentMeta))
logMessage += Global.DeviceDatasets.getDiff(previousData=previousMeta, currentData=currentMetaNames, folder="Meta")
currentAlarms = Global.DeviceDatasets.generateDeviceDataset(
deviceTagpath="{referenceTagpath}/Alarms".format(referenceTagpath=referenceTagpath),
datasetTagpath="{datasetTagpath}/Alarms".format(datasetTagpath=datasetTagpath),
writeToSystem=True,
)
if debug:
currentAlarmsNames = list(map(lambda x: str(x.get("tagName", "N/A")), currentAlarms))
logMessage += Global.DeviceDatasets.getDiff(previousData=previousAlarms, currentData=currentAlarmsNames, folder="Alarms")
if len(logMessage) == 0:
logMessage = "{} Successfully generated.".format(timestamp)
# Reset the _Manual Refresh tag back to False.
system.tag.writeAsync([datasetTagpath + "/_Manual Refresh"], [False])
# Write the aggregate log message to _Log Message.
system.tag.writeAsync([datasetTagpath + "/_Log Message"], [logMessage])
return logMessage