From e9843607db87254c1d167e1213b98543bf3188c0 Mon Sep 17 00:00:00 2001 From: Salijoghli <107577102+Salijoghli@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:25:37 +0400 Subject: [PATCH] Added GetActiveAlarms named query, removed extra scripts in the active alarm table --- .../views/Alarm-Views/RealTime/view.json | 140 +++++++++++------- .../named-query/GetActiveAlarms/query.sql | 60 ++++++++ .../named-query/GetActiveAlarms/resource.json | 40 +++++ .../ignition/named-query/GetAlarms/query.sql | 2 + .../named-query/GetAlarmsWithCount/query.sql | 2 + 5 files changed, 188 insertions(+), 56 deletions(-) create mode 100644 SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql create mode 100644 SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/resource.json diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json index e74d764..d6f97c7 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Alarm-Views/RealTime/view.json @@ -488,6 +488,7 @@ ], "custom": { "priorities": { + "critical": false, "diagnostic": false, "high": false, "low": false, @@ -599,7 +600,7 @@ "component": { "onActionPerformed": { "config": { - "script": "\ttry:\n\t # Get table data\n\t data \u003d self.parent.parent.parent.getChild(\"FlexContainer_0\").getChild(\"Table\").props.data\n\t\n\t # CSV header\n\t csv_content \u003d \"Number (ID),Event Timestamp,Duration,Priority,Description,Tag\\n\"\n\t\n\t if data and len(data) \u003e 0:\n\t for row in data:\n\t val \u003d row.get(\"value\", {})\n\t row_data \u003d [\n\t str(val.get(\"NumberID\", \"\")),\n\t str(val.get(\"EventTimestamp\", \"\")),\n\t str(val.get(\"Duration\", \"\")),\n\t str(val.get(\"Priority\", \"\")),\n\t str(val.get(\"Description\", \"\")),\n\t str(val.get(\"Tag\", \"\"))\n\t ]\n\t\n\t # Escape commas for CSV safety\n\t row_data \u003d [field.replace(\",\", \";\") for field in row_data]\n\t csv_content +\u003d \",\".join(row_data) + \"\\n\"\n\t else:\n\t csv_content +\u003d \"No alarms in current view\\n\"\n\t\n\texcept Exception as e:\n\t system.perspective.print(\"Error during CSV export: \" + str(e))\n\t csv_content \u003d \"Error exporting alarm data\\n\"\n\t\n\t# Convert to bytes and trigger download\n\tcsv_bytes \u003d csv_content.encode(\u0027utf-8\u0027)\n\tsystem.perspective.download(\"active_alarms.csv\", csv_bytes)\n " + "script": "\tfrom datetime import datetime\n\ttry:\n\t # Get table data\n\t data \u003d self.parent.parent.parent.getChild(\"FlexContainer_0\").getChild(\"Table\").props.data\n\t \n\t column_order \u003d [\n\t \"ID\",\n\t \"StartTimestamp\", \n\t \"Duration\",\n\t \"Priority\",\n\t \"Location\",\n\t \"Description\",\n\t \"Tag\"\n\t ]\n\t\n\t # CSV header\n\t csv_content \u003d \",\".join(column_order) + \"\\n\"\n\t \n\t def unwrap(v):\n\t\t\tif hasattr(v, \u0027value\u0027):\n\t\t\t\treturn str(v.value)\n\t \t\n\t\t\treturn v\n\t \n\t if data and len(data) \u003e 0:\n\t for item in data:\n\t row_data \u003d []\n\t \n\t for col in column_order:\n\t # Look for the column in the current item\n\t if col in item:\n\t cell \u003d item[col]\n\t # Extract the value from the nested structure\n\t if isinstance(cell, dict) and \"value\" in cell:\n\t raw_value \u003d cell[\"value\"]\n\t else:\n\t raw_value \u003d cell\n\t else:\n\t raw_value \u003d \"\"\n\t \n\t # Process and clean the value\n\t processed_value \u003d unwrap(raw_value).replace(\",\", \";\")\n\t row_data.append(processed_value)\n\t \n\t csv_content +\u003d \",\".join(row_data) + \"\\n\"\n\t else:\n\t csv_content +\u003d \"No alarms in current view\\n\"\n\t\n\texcept Exception as e:\n\t system.perspective.print(\"Export Error: \" + str(e))\n\t csv_content \u003d \"Export failed\\n\"\n\t\n\tcsv_bytes \u003d csv_content.encode(\"utf-8\")\n\tsystem.perspective.download(\"active_alarms.csv\", csv_bytes) \n\t \n\t \n\t \n\t\n#\t if data and len(data) \u003e 0:\n#\t for row in data:\n#\t val \u003d row.get(\"value\", {})\n#\t row_data \u003d [\n#\t str(val.get(\"NumberID\", \"\")),\n#\t str(val.get(\"EventTimestamp\", \"\")),\n#\t str(val.get(\"Duration\", \"\")),\n#\t str(val.get(\"Priority\", \"\")),\n#\t str(val.get(\"Description\", \"\")),\n#\t str(val.get(\"Tag\", \"\"))\n#\t ]\n#\t\n#\t # Escape commas for CSV safety\n#\t row_data \u003d [field.replace(\",\", \";\") for field in row_data]\n#\t csv_content +\u003d \",\".join(row_data) + \"\\n\"\n#\t else:\n#\t csv_content +\u003d \"No alarms in current view\\n\"\n#\t\n#\texcept Exception as e:\n#\t system.perspective.print(\"Error during CSV export: \" + str(e))\n#\t csv_content \u003d \"Error exporting alarm data\\n\"\n#\t\n#\t# Convert to bytes and trigger download\n#\tcsv_bytes \u003d csv_content.encode(\u0027utf-8\u0027)\n#\tsystem.perspective.download(\"active_alarms.csv\", csv_bytes)\n " }, "scope": "G", "type": "script" @@ -691,24 +692,6 @@ { "children": [ { - "custom": { - "rawData": [ - { - "style": { - "classes": "Alarms-Styles/Low" - }, - "value": { - "Description": "MCM01 Hello world", - "Duration": "00:05:48", - "EventTimestamp": "2025-06-23 13:10:26", - "Location": "MCM01", - "NumberID": 60, - "Priority": "Low", - "Tag": "MCM01.HMI.Beacon_Light" - } - } - ] - }, "meta": { "name": "Table" }, @@ -721,33 +704,34 @@ "config": { "path": ".../FlexContainer/FlexContainer.custom.priorities" }, - "type": "property" - } - }, - "props.data": { - "binding": { - "config": { - "path": "this.custom.rawData" - }, "transforms": [ { - "code": "\t\n\tpriorities \u003d self.custom.priorities if hasattr(self.custom, \"priorities\") else []\n\t\n\tactivePriorities \u003d [k.capitalize() for k, v in priorities.items() if v]\n\t\n\tif not activePriorities:\n\t return value\n\t\n\tfiltered_data \u003d []\n\t\n\tfor row in value:\n\t\tpriority \u003d row[\"value\"][\"Priority\"]\n\t\tif priority in activePriorities:\n\t \t filtered_data.append(row)\n\t\n\treturn filtered_data", + "code": "\t\n\tpriority_to_number \u003d {\n\t \"critical\": 4,\n\t \"high\": 3,\n\t \"medium\": 2,\n\t \"low\": 1,\n\t \"diagnostic\": 0\n\t}\n\t\n\t\n\t# Collect enabled priorities\n\tenabled \u003d [str(priority_to_number[k]) for k, v in value.items() if v]\n\t\n\tresult \u003d \",\".join(enabled)\n\t\n\tif not result:\n\t\treturn \"\"\n\t\n\treturn result\n\t\n", "type": "script" } ], "type": "property" } }, - "props.query": { + "props.data": { "binding": { "config": { - "expression": "now(2000)" + "parameters": { + "priorityList": "{this.custom.priorities}" + }, + "polling": { + "enabled": true, + "rate": "3" + }, + "queryPath": "GetActiveAlarms" }, - "type": "expr" - }, - "onChange": { - "enabled": null, - "script": "\tfrom system import date\n\t\n\ttag_provider \u003d \"[\" + self.session.custom.fc + \"_SCADA_TAG_PROVIDER]\"\n\n\t# Helper: format row for table\n\tdef testRow(eventTimeStamp, duration, location, priority, description, tag, className, numberId):\n\t\treturn {\n\t\t \"value\": {\n\t\t \"NumberID\": numberId,\n\t\t \"EventTimestamp\": eventTimeStamp,\n\t\t \"Duration\": duration,\n\t\t \"Priority\": priority,\n\t\t \"Location\": location,\n\t\t \"Description\": description,\n\t\t \"Tag\": tag,\n\t\t },\n\t\t \"style\": {\n\t\t \"classes\": className,\n\t\t }\n\t\t}\n\t\n\t# Query active alarms\n\tresults \u003d system.alarm.queryStatus(state\u003d[\"ActiveUnacked\", \"ActiveAcked\"])\n\t\n\t# Build rows\n\tdata \u003d []\n\tnumberID \u003d \"\"\n\tlcoation \u003d \"\"\n\tclassName \u003d \"\"\n\tif(results \u003d\u003d0):\n\t\treturn\n\t\n\tfor i, alarm in enumerate(results):\n\t\tactiveTime \u003d alarm.eventTime\n\t\tmyTag \u003d alarm.myTag\n\t\tOPCItemTag \u003d system.tag.read(tag_provider + myTag + \".OpcItemPath\").value\n\t\tif(OPCItemTag):\t\t\n\t\t\tparts \u003d OPCItemTag.split(\".\")\n\t\t\ttag \u003d str(alarm.displayPath) +\".\" + parts[1] + \".\" + parts[2]\n\t\telse:\n\t\t\ttag \u003d \"Unknown tag\"\n\t\t\n\t\ttag_parts \u003d myTag.split(\"/\")\n\t\tif(tag_parts):\n\t\t\tlocation \u003d tag_parts[1] \n\t\telse:\n\t\t\tlocation \u003d \"Unknown Location\"\n\t\t\n\t\t\n\t\ttry:\n\t\t query \u003d system.db.runQuery(\"SELECT id, eventtime FROM alarm_events WHERE eventid \u003d \" + \"\u0027\" + str(alarm.id) + \"\u0027\",\"MariaDB\")\n\t\t if(query):\n\t\t \tnumberID \u003d query[0][0]\n\t\t durationSeconds \u003d date.secondsBetween(activeTime, date.now())\n\t\t durationStr \u003d date.format(date.addSeconds(date.midnight(date.now()), durationSeconds), \"HH:mm:ss\")\n\t\t \n\t\t priority \u003d alarm.priority.toString()\n\t\t if priority \u003d\u003d \"High\":\n\t\t color \u003d \"Alarms-Styles/High\"\n\t\t elif priority \u003d\u003d \"Medium\":\n\t\t className \u003d \"Alarms-Styles/Medium\"\n\t\t elif priority \u003d\u003d \"Low\":\n\t\t className \u003d \"Alarms-Styles/Low\"\n\t\t elif priority \u003d\u003d \"Diagnostic\":\n\t\t className \u003d \"Alarms-Styles/Diagnostic\"\n\t\t else:\n\t\t className \u003d \"Alarms-Styles/NoAlarm\"\n\t\t \n\t\n\t\t data.append(testRow(\n\t\t eventTimeStamp \u003d date.format(activeTime, \"yyyy-MM-dd HH:mm:ss\"),\n\t\t duration \u003d durationStr,\n\t\t location \u003d location,\n\t\t numberId \u003d numberID,\n\t\t priority \u003d priority,\n\t\t description \u003d alarm.myLocation + \" \" + alarm.name,\n\t\t tag \u003d tag,\n\t\t className \u003d className,\n\t\t ))\n\t\texcept:\n\t\t\tpass\n\t\n\tself.custom.rawData \u003d data" + "transforms": [ + { + "code": "\n\tfrom system.dataset import toPyDataSet\n\n\tds \u003d toPyDataSet(value)\n\tdata \u003d []\n\n\tcolumn_names \u003d [col for col in ds.columnNames if col !\u003d \"EndTimestamp\"]\n\t\n\n\tfor row in ds:\n\t\tpriority \u003d row[\"Priority\"]\n\n\t\t# Use style class names from Perspective\n\t\tif priority \u003d\u003d \"High\":\n\t\t\tclassName \u003d \"Alarms-Styles/High\"\n\t\telif priority \u003d\u003d \"Medium\":\n\t\t\tclassName \u003d \"Alarms-Styles/Medium\"\n\t\telif priority \u003d\u003d \"Low\":\n\t\t\tclassName \u003d \"Alarms-Styles/Low\"\n\t\telif priority \u003d\u003d \"Diagnostic\":\n\t\t\tclassName \u003d \"Alarms-Styles/Diagnostic\"\n\t\telse:\n\t\t\tclassName \u003d \"Alarms-Styles/NoAlarm\"\n\n\t\t# Apply the style class to all cells in the row\n\t\trow_dict \u003d {\n\t\t\tcol: {\n\t\t\t\t\"value\": row[col],\n\t\t\t\t\"style\": { \"classes\": className }\n\t\t\t} for col in column_names\n\t\t}\n\t\tdata.append(row_dict)\n\n\treturn data", + "type": "script" + } + ], + "type": "query" } } }, @@ -848,9 +832,9 @@ { "align": "center", "boolean": "checkbox", - "dateFormat": "MM/DD/YYYY", + "dateFormat": "MM/DD/YYYY HH:mm:ss", "editable": false, - "field": "EventTimestamp", + "field": "StartTimestamp", "filter": { "boolean": { "condition": "" @@ -1401,28 +1385,73 @@ "emptyMessage": { "noData": { "text": "No Active Alarms" + }, + "noFilterResults": { + "text": "No Active Alarms" } }, "filter": { "enabled": true, "results": { - "data": [ - { - "Description": "MCM01 Hello world", - "Duration": "00:03:48", - "EventTimestamp": "2025-06-23 13:10:26", - "Location": "MCM01", - "NumberID": 60, - "Priority": "Low", - "Tag": "MCM01.HMI.Beacon_Light" - } - ], "enabled": true - }, - "text": " " + } }, "selection": { - "mode": "multiple interval" + "data": [ + { + "Description": { + "style": { + "classes": "Alarms-Styles/High" + }, + "value": "MCM01 - Hello world" + }, + "Duration": { + "style": { + "classes": "Alarms-Styles/High" + }, + "value": "00:53:47" + }, + "ID": { + "style": { + "classes": "Alarms-Styles/High" + }, + "value": 103 + }, + "Location": { + "style": { + "classes": "Alarms-Styles/High" + }, + "value": "MCM01" + }, + "Priority": { + "style": { + "classes": "Alarms-Styles/High" + }, + "value": "High" + }, + "StartTimestamp": { + "style": { + "classes": "Alarms-Styles/High" + }, + "value": { + "$": [ + "ts", + 0, + 1750677703466 + ], + "$ts": 1750674476000 + } + }, + "Tag": { + "style": { + "classes": "Alarms-Styles/High" + }, + "value": "MCM01.HMI.Beacon_Light" + } + } + ], + "mode": "multiple interval", + "selectedRow": 0 } }, "type": "ia.display.table" @@ -2923,7 +2952,7 @@ "$": [ "ts", 192, - 1750667761263 + 1750674251724 ], "$ts": 1750435156149 }, @@ -2931,7 +2960,7 @@ "$": [ "ts", 192, - 1750667761263 + 1750674251724 ], "$ts": 1750436956149 } @@ -3871,7 +3900,7 @@ "$": [ "ts", 192, - 1750667761263 + 1750674251724 ], "$ts": 1750435156149 }, @@ -3879,7 +3908,7 @@ "$": [ "ts", 192, - 1750667761263 + 1750674251724 ], "$ts": 1750436956149 }, @@ -4138,7 +4167,6 @@ "contentStyle": { "classes": "Background-Styles/Grey-Background" }, - "currentTabIndex": 2, "menuType": "modern", "style": { "classes": "Background-Styles/Grey-Background" diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql new file mode 100644 index 0000000..d3c2165 --- /dev/null +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/query.sql @@ -0,0 +1,60 @@ +WITH Active AS ( + SELECT + ae.id, + ae.eventtime, + ae.eventid, + ae.source, + ae.priority, + ae.displaypath, + TIMESTAMPDIFF(SECOND, ae.eventtime, NOW()) AS duration_seconds + FROM alarm_events ae + WHERE ae.eventtype = 0 + AND NOT EXISTS ( + SELECT 1 FROM alarm_events ae_clear + WHERE ae_clear.eventid = ae.eventid + AND ae_clear.eventtype = 1 + ) + AND ae.displaypath NOT LIKE '%System Startup%' + AND ae.source NOT LIKE '%System Startup%' + -- Priority filter using FIND_IN_SET for comma-separated values + AND ( + :priorityList IS NULL + OR :priorityList = '' + OR FIND_IN_SET(ae.priority, :priorityList) > 0 + ) + GROUP BY ae.id +), +SingleMyTag AS ( + SELECT aed.id, aed.strValue + FROM alarm_event_data aed + WHERE aed.propname = 'myTag' + GROUP BY aed.id +) +SELECT + Active.id AS ID, + Active.eventtime AS StartTimestamp, + NULL AS EndTimestamp, -- no end time since it's still active + CONCAT( + LPAD(FLOOR(Active.duration_seconds / 3600), 2, '0'), ':', + LPAD(FLOOR((Active.duration_seconds % 3600) / 60), 2, '0'), ':', + LPAD(Active.duration_seconds % 60, 2, '0') + ) AS Duration, + CONCAT(Active.displaypath, ' - ', SUBSTRING_INDEX(Active.source, ':/alm:', -1)) AS Description, + CASE Active.priority + WHEN 0 THEN 'Diagnostic' + WHEN 1 THEN 'Low' + WHEN 2 THEN 'Medium' + WHEN 3 THEN 'High' + WHEN 4 THEN 'Critical' + ELSE 'Unknown' + END AS Priority, + CONCAT( + Active.displaypath, + '.HMI.', + SUBSTRING_INDEX(aed.strValue, '/', -1) + ) AS Tag, + SUBSTRING_INDEX(SUBSTRING_INDEX(aed.strValue, '/', 2), '/', -1) AS Location +FROM Active +LEFT JOIN SingleMyTag aed + ON aed.id = Active.id +ORDER BY Active.eventtime DESC; \ No newline at end of file diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/resource.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/resource.json new file mode 100644 index 0000000..9fc8d6e --- /dev/null +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetActiveAlarms/resource.json @@ -0,0 +1,40 @@ +{ + "scope": "DG", + "version": 2, + "restricted": false, + "overridable": true, + "files": [ + "query.sql" + ], + "attributes": { + "useMaxReturnSize": false, + "autoBatchEnabled": false, + "fallbackValue": "", + "maxReturnSize": 100, + "cacheUnit": "SEC", + "type": "Query", + "enabled": true, + "cacheAmount": 1, + "cacheEnabled": false, + "database": "MariaDB", + "fallbackEnabled": false, + "lastModificationSignature": "d4eef6a8194fc16fb2061c0ffe057909fb37ddedbe4fd6e4f2416cc6050f6209", + "permissions": [ + { + "zone": "default", + "role": "" + } + ], + "lastModification": { + "actor": "admin", + "timestamp": "2025-06-23T10:38:45Z" + }, + "parameters": [ + { + "type": "Parameter", + "identifier": "priorityList", + "sqlType": 7 + } + ] + } +} \ No newline at end of file diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql index 165ff92..3f3a3e2 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarms/query.sql @@ -11,6 +11,8 @@ WITH Active AS ( LEFT JOIN alarm_events ae_clear ON ae.eventid = ae_clear.eventid AND ae_clear.eventtype = 1 WHERE ae.eventtype = 0 + AND ae.displaypath NOT LIKE '%System Startup%' + AND ae.source NOT LIKE '%System Startup%' GROUP BY ae.id -- Ensure one row per alarm ), SingleMyTag AS ( diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql index 28a2e21..fc32678 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/ignition/named-query/GetAlarmsWithCount/query.sql @@ -46,6 +46,8 @@ FROM ( LEFT JOIN alarm_events ae_clear ON ae.eventid = ae_clear.eventid AND ae_clear.eventtype = 1 WHERE ae.eventtype = 0 + AND ae.displaypath NOT LIKE '%System Startup%' + AND ae.source NOT LIKE '%System Startup%' ) AS Active -- OPC tag path for building .hmi.Tag output