From 44746de5ee62fed448cd8a8ae4ea74d73270771e Mon Sep 17 00:00:00 2001 From: gugak <107577102+Salijoghli@users.noreply.github.com> Date: Fri, 16 May 2025 18:00:21 +0400 Subject: [PATCH 1/6] fixed database error, no more errors every second if connection is lost --- .../views/Windows/Statistics/view.json | 438 +++--------------- 1 file changed, 52 insertions(+), 386 deletions(-) diff --git a/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json b/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json index a854688..dbcd735 100644 --- a/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json +++ b/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json @@ -1,7 +1,7 @@ { "custom": {}, "params": { - "Tab_ID": 11, + "Tab_ID": 9, "Table": "Statistics" }, "propConfig": { @@ -55,7 +55,7 @@ }, "onChange": { "enabled": null, - "script": "\t\t\t\n\t\t\t\t\n\tpath \u003d \"\"\n\theaders \u003d []\n\tgraph \u003d []\n\t\n\tif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t\tpath \u003d \"Statistics/Hourly Scanner Count\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Good Read (#)\",\"No Read (#)\",\"Multi Read (#)\",\"No Code (#)\"]\n\t\tself.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n\telif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t\tpath \u003d \"Statistics/Hourly Scanner Percent\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Good Read (%)\",\"No Read (%)\",\"Multi Read (%)\",\"No Code (%)\"]\n\t\tself.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d 100\n\telse:\n\t\tpath \u003d \"Statistics/Hourly Scanner Rate\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Good Read (pph)\",\"No Read (pph)\",\"Multi Read (pph)\",\"No Code (pph)\"]\n\t\tself.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (pph): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n\t\t\n\tparams \u003d {\"starttime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\"endtime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate}\n\t\n\tdata \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path,params))\n\tfor row in data:\t\n\t\tdict \u003d {}\n\t\tdict[\u0027Hour\u0027] \u003d \trow[\u0027Hour\u0027]\n\t\tdict[\u0027GoodRead\u0027] \u003d row[\u0027GoodRead\u0027]\n\t\tdict[\u0027NoRead\u0027] \u003d row[\u0027NoRead\u0027]\n\t\tdict[\u0027MultiRead\u0027] \u003d row[\u0027MultiRead\u0027]\n\t\tdict[\u0027NoCode\u0027] \u003d row[\u0027NoCode\u0027]\n\t\tgraph.append(dict)\n\t\n\t\n\tself.getSibling(\"Hourly Scanner\").props.dataSources.example \u003d graph\n\tself.props.data \u003d system.dataset.toDataSet(headers,data)" + "script": "\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n # 1. Database Connection Check\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n try:\n # Check if MariaDB exists and is ready\n connections \u003d system.db.getConnections()\n maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n \n if not maria_conn:\n print(\"❌ MariaDB connection not configured in Ignition\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n return\n \n if maria_conn.status \u003d\u003d \"Disabled\":\n print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n system.db.enableConnection(\"MariaDB\")\n system.util.sleep(2000) # Wait for connection attempt\n \n if maria_conn.status !\u003d \"Connected\":\n error_msg \u003d \"MariaDB status: {maria_conn.status}\"\n print(\"{error_msg}\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n return\n \n # Quick ping test (3 second timeout)\n if not system.db.ping(\"MariaDB\", 3000):\n raise Exception(\"Database not responding to ping\")\n \n except Exception as e:\n error_msg \u003d \"Database connection failed: {str(e)}\"\n print(\"{error_msg}\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n return\n\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n # 2. Original Logic (Protected)\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n try:\n path \u003d \"\"\n headers \u003d []\n graph \u003d []\n \n # Mode selection (unchanged)\n if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n path \u003d \"Statistics/Hourly Scanner Count\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Good Read (#)\",\"No Read (#)\",\"Multi Read (#)\",\"No Code (#)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n path \u003d \"Statistics/Hourly Scanner Percent\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Good Read (%)\",\"No Read (%)\",\"Multi Read (%)\",\"No Code (%)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d 100\n else:\n path \u003d \"Statistics/Hourly Scanner Rate\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Good Read (pph)\",\"No Read (pph)\",\"Multi Read (pph)\",\"No Code (pph)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (pph): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n \n params \u003d {\n \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n }\n \n # Execute query with error handling\n data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n \n # Process data (unchanged)\n graph \u003d []\n for row in data: \n dict \u003d {\n \u0027Hour\u0027: row[\u0027Hour\u0027],\n \u0027GoodRead\u0027: row[\u0027GoodRead\u0027],\n \u0027NoRead\u0027: row[\u0027NoRead\u0027],\n \u0027MultiRead\u0027: row[\u0027MultiRead\u0027],\n \u0027NoCode\u0027: row[\u0027NoCode\u0027]\n }\n graph.append(dict)\n \n # Update components\n self.getSibling(\"Hourly Scanner\").props.dataSources.example \u003d graph\n self.props.data \u003d system.dataset.toDataSet(headers, data)\n \n except Exception as e:\n error_msg \u003d \"Query failed: {str(e)}\"\n print(\"🚨 {error_msg}\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" } }, "custom.time": { @@ -618,57 +618,15 @@ "$": [ "ds", 192, - 1747222130171 + 1747403903257 ], "$columns": [ { "data": [ - "2025-05-14 14:00" + "Database connection failed: {str(e)}" ], - "name": "Start Timestamp", + "name": "Error", "type": "String" - }, - { - "data": [ - "H1" - ], - "name": "Hour", - "type": "String" - }, - { - "data": [ - 4 - ], - "name": "Total (#)", - "type": "Long" - }, - { - "data": [ - 4 - ], - "name": "Good Read (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "No Read (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Multi Read (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "No Code (#)", - "type": "Double" } ] }, @@ -697,15 +655,7 @@ }, "props": { "dataSources": { - "example": [ - { - "GoodRead": 4, - "Hour": "H1", - "MultiRead": 0, - "NoCode": 0, - "NoRead": 0 - } - ] + "example": [] }, "series": [ { @@ -1961,7 +1911,7 @@ }, "onChange": { "enabled": null, - "script": " path \u003d \"\"\n headers \u003d []\n rows \u003d []\n\n # Define the appropriate path based on the selected aggregation mode\n if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n path \u003d \"Statistics/Hourly Induct Count\"\n headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (#)\"]\n self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n path \u003d \"Statistics/Hourly Induct Percent\"\n headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (%)\"]\n self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d 100\n else:\n path \u003d \"Statistics/Hourly Induct Rate\"\n headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (pph)\"]\n self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n\n # Fetch the data from the database\n params \u003d {\"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate, \n \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate}\n data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\n # Prepare the rows for the dataset without \u0027SingleCarrier\u0027 and \u0027DoubleCarrier\u0027\n for row in data:\n row_data \u003d []\n row_data.append(row[\u0027StartTimestamp\u0027])\n row_data.append(row[\u0027Hour\u0027])\n \n # Add the total count or percentage\n if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n row_data.append(row[\u0027Total_count\u0027]) # For \"Count\" mode, add \u0027Total_count\u0027\n elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n row_data.append(row[\u0027Total_percentage\u0027]) # Replace with correct column name for percentage\n else:\n row_data.append(row[\u0027Total_pph\u0027]) # Replace with correct column name for rate (pph)\n\n # Only add SingleCarrier and DoubleCarrier if they exist and need to be shown\n if \u0027SingleCarrier\u0027 in row and row[\u0027SingleCarrier\u0027] is not None:\n row_data.append(row[\u0027SingleCarrier\u0027])\n headers.append(\"Single Carrier (#)\") # Only add header if the column exists and is required\n if \u0027DoubleCarrier\u0027 in row and row[\u0027DoubleCarrier\u0027] is not None:\n row_data.append(row[\u0027DoubleCarrier\u0027])\n headers.append(\"Double Carrier (#)\") # Only add header if the column exists and is required\n\n rows.append(row_data)\n\n # Remove the \"Single Carrier\" and \"Double Carrier\" columns from the dataset\n # If they are present in the headers and rows, you can filter them out before final conversion\n\n # Remove unwanted columns from headers\n filtered_headers \u003d [header for header in headers if \"Single Carrier\" not in header and \"Double Carrier\" not in header]\n\n # Filter the rows by removing the corresponding columns (Single Carrier and Double Carrier)\n filtered_rows \u003d []\n for row in rows:\n filtered_row \u003d [value for index, value in enumerate(row) if headers[index] not in [\"Single Carrier (#)\", \"Double Carrier (#)\"]]\n filtered_rows.append(filtered_row)\n\n # Create the dataset without \"Single Carrier\" and \"Double Carrier\" columns\n dataset \u003d system.dataset.toDataSet(filtered_headers, filtered_rows)\n \n # Update the dataset and chart data\n self.props.data \u003d dataset\n self.getSibling(\"Hourly Induct\").props.dataSources.example \u003d dataset" + "script": "\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 1. Database Connection Check\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t # Check if MariaDB exists and is ready\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t \n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t \n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000) # Wait for connection attempt\n\t \n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {}\".format(maria_conn.status)\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t \n\t # Quick ping test (3 second timeout)\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t \n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {}\".format(str(e))\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 2. Original Logic (Wrapped in Try Block)\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t path \u003d \"\"\n\t headers \u003d []\n\t rows \u003d []\n\t\n\t # Define the appropriate path based on the selected aggregation mode\n\t mode \u003d self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value\n\t if mode \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Induct Count\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (#)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif mode \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Induct Percent\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (%)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Induct Rate\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (pph)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t # Fetch the data from the database\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate, \n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t # Prepare the rows for the dataset without \u0027SingleCarrier\u0027 and \u0027DoubleCarrier\u0027\n\t for row in data:\n\t row_data \u003d [row[\u0027StartTimestamp\u0027], row[\u0027Hour\u0027]]\n\t \n\t if mode \u003d\u003d \"Count\":\n\t row_data.append(row[\u0027Total_count\u0027])\n\t elif mode \u003d\u003d \"Percentage\":\n\t row_data.append(row[\u0027Total_percentage\u0027])\n\t else:\n\t row_data.append(row[\u0027Total_pph\u0027])\n\t\n\t if \u0027SingleCarrier\u0027 in row and row[\u0027SingleCarrier\u0027] is not None:\n\t row_data.append(row[\u0027SingleCarrier\u0027])\n\t headers.append(\"Single Carrier (#)\")\n\t if \u0027DoubleCarrier\u0027 in row and row[\u0027DoubleCarrier\u0027] is not None:\n\t row_data.append(row[\u0027DoubleCarrier\u0027])\n\t headers.append(\"Double Carrier (#)\")\n\t\n\t rows.append(row_data)\n\t\n\t # Filter headers and rows to exclude unwanted columns\n\t filtered_headers \u003d [h for h in headers if h not in [\"Single Carrier (#)\", \"Double Carrier (#)\"]]\n\t filtered_rows \u003d [\n\t [val for idx, val in enumerate(r) if headers[idx] not in [\"Single Carrier (#)\", \"Double Carrier (#)\"]]\n\t for r in rows\n\t ]\n\t\n\t dataset \u003d system.dataset.toDataSet(filtered_headers, filtered_rows)\n\t\n\t # Update the dataset and chart data\n\t self.props.data \u003d dataset\n\t self.getSibling(\"Hourly Induct\").props.dataSources.example \u003d dataset\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {}\".format(str(e))\n\t print(\"🚨 {}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" } }, "custom.time": { @@ -2184,29 +2134,15 @@ "$": [ "ds", 192, - 1747222130136 + 1747403903262 ], "$columns": [ { "data": [ - "2025-05-14 14:00" + "Database connection failed: \u0027com.inductiveautomation.ignition.common.BasicDataset\u0027 object is not iterable" ], - "name": "Start Timestamp", + "name": "Error", "type": "String" - }, - { - "data": [ - "H1" - ], - "name": "Hour", - "type": "String" - }, - { - "data": [ - 4 - ], - "name": "Total (#)", - "type": "Long" } ] }, @@ -2242,29 +2178,23 @@ "$": [ "ds", 192, - 1747222130137 + 1747402203240 ], "$columns": [ { - "data": [ - "2025-05-14 14:00" - ], + "data": [], "name": "Start Timestamp", "type": "String" }, { - "data": [ - "H1" - ], + "data": [], "name": "Hour", "type": "String" }, { - "data": [ - 4 - ], + "data": [], "name": "Total (#)", - "type": "Long" + "type": "String" } ] } @@ -3006,7 +2936,7 @@ }, "onChange": { "enabled": null, - "script": "\t\n\tpath \u003d \"\"\n\theaders \u003d []\n\tgraph \u003d []\n\t\n\tif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t\tpath \u003d \"Statistics/Hourly Sorter Summary Count\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (#)\",\"Sorted (#)\",\"Awcs Recirc (#)\",\"Operational Recirc (#)\",\"Machine Recirc (#)\"]\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\telif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t\tpath \u003d \"Statistics/Hourly Sorter Summary Percent\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (%)\",\"Sorted (%)\",\"Awcs Recirc (%)\",\"Operational Recirc (%)\",\"Machine Recirc (%)\"]\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d 100\n\telse:\n\t\tpath \u003d \"Statistics/Hourly Sorter Summary Rate\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (pph)\",\"Sorted (pph)\",\"Awcs Recirc (pph)\",\"Operational Recirc (pph)\",\"Machine Recirc (pph)\"]\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\t\t\n\tparams \u003d {\"starttime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\"endtime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate}\n\t\n\tdata \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path,params))\n\tfor row in data:\t\n\t\tdict \u003d {}\n\t\tdict[\u0027Hour\u0027] \u003d \trow[\u0027Hour\u0027]\n\t\tdict[\u0027Sorted\u0027] \u003d row[\u0027Sorted\u0027]\n\t\tdict[\u0027AwcsRecirc\u0027] \u003d row[\u0027AwcsRecirc\u0027]\n\t\tdict[\u0027OperationalRecirc\u0027] \u003d row[\u0027OperationalRecirc\u0027]\n\t\tdict[\u0027MachineRecirc\u0027] \u003d row[\u0027MachineRecirc\u0027]\n\t\tgraph.append(dict)\n\t\n\t\n\tself.getSibling(\"Hourly Sorter Summary\").props.dataSources.example \u003d graph\n\tself.props.data \u003d system.dataset.toDataSet(headers,data)" + "script": "\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 1. Database Connection Check\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t\n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t\n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000)\n\t\n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {maria_conn.status}\"\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {str(e)}\"\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 2. Original Logic\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t path \u003d \"\"\n\t headers \u003d []\n\t graph \u003d []\n\t\n\t mode \u003d self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value\n\t\n\t if mode \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Sorter Summary Count\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (#)\", \"Sorted (#)\", \"Awcs Recirc (#)\", \"Operational Recirc (#)\", \"Machine Recirc (#)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif mode \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Sorter Summary Percent\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (%)\", \"Sorted (%)\", \"Awcs Recirc (%)\", \"Operational Recirc (%)\", \"Machine Recirc (%)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Sorter Summary Rate\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (pph)\", \"Sorted (pph)\", \"Awcs Recirc (pph)\", \"Operational Recirc (pph)\", \"Machine Recirc (pph)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t for row in data:\n\t entry \u003d {\n\t \"Hour\": row[\"Hour\"],\n\t \"Sorted\": row[\"Sorted\"],\n\t \"AwcsRecirc\": row[\"AwcsRecirc\"],\n\t \"OperationalRecirc\": row[\"OperationalRecirc\"],\n\t \"MachineRecirc\": row[\"MachineRecirc\"]\n\t }\n\t graph.append(entry)\n\t\n\t self.getSibling(\"Hourly Sorter Summary\").props.dataSources.example \u003d graph\n\t self.props.data \u003d system.dataset.toDataSet(headers, data)\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {str(e)}\"\n\t print(\"🚨 {error_msg}\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" } }, "custom.time": { @@ -3569,57 +3499,15 @@ "$": [ "ds", 192, - 1747222130439 + 1747403903257 ], "$columns": [ { "data": [ - "2025-05-14 14:00" + "Database connection failed: {str(e)}" ], - "name": "Start Timestamp", + "name": "Error", "type": "String" - }, - { - "data": [ - "H1" - ], - "name": "Hour", - "type": "String" - }, - { - "data": [ - 4 - ], - "name": "Inducted (#)", - "type": "Long" - }, - { - "data": [ - 4 - ], - "name": "Sorted (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Awcs Recirc (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Operational Recirc (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Machine Recirc (#)", - "type": "Double" } ] }, @@ -3647,15 +3535,7 @@ }, "props": { "dataSources": { - "example": [ - { - "AwcsRecirc": 0, - "Hour": "H1", - "MachineRecirc": 0, - "OperationalRecirc": 0, - "Sorted": 4 - } - ] + "example": [] }, "series": [ { @@ -4911,7 +4791,7 @@ }, "onChange": { "enabled": null, - "script": "\t\n\tpath \u003d \"\"\n\theaders \u003d []\n\tgraph \u003d []\n\t\n\tif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t\tpath \u003d \"Statistics/Hourly Sorter Details Count\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (#)\",\"Sorted (#)\",\"Dest Inv (#)\",\"Dest None (#)\",\"Dest Dis (#)\",\"Dest Full (#)\",\"Unexpected (#)\",\"Dest Fault (#)\",\"Div Fail (#)\",\"Gap Err (#)\",\"Lost (#)\",\"Track Err (#)\",\"Unknown (#)\",\"Unsafe (#)\"]\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\telif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t\tpath \u003d \"Statistics/Hourly Sorter Details Percent\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (%)\",\"Sorted (%)\",\"Dest Inv (%)\",\"Dest None (%)\",\"Dest Dis (%)\",\"Dest Full (%)\",\"Unexpected (%)\",\"Dest Fault (%)\",\"Div Fail (%)\",\"Gap Err (%)\",\"Lost (%)\",\"Track Err (%)\",\"Unknown (%)\",\"Unsafe (%)\"]\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d 100\n\telse:\n\t\tpath \u003d \"Statistics/Hourly Sorter Details Rate\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (pph)\",\"Sorted (pph)\",\"Dest Inv (pph)\",\"Dest None (pph)\",\"Dest Dis (pph)\",\"Dest Full (pph)\",\"Unexpected (pph)\",\"Dest Fault (pph)\",\"Div Fail (pph)\",\"Gap Err (pph)\",\"Lost (pph)\",\"Track Err (pph)\",\"Unknown (pph)\",\"Unsafe (pph)\"]\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/]pph\"\n\t\tself.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\t\t\n\tparams \u003d {\"starttime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\"endtime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate}\t\n\t\n\tdata \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path,params))\n\tfor row in data:\t\n\t\tdict \u003d {}\n\t\tdict[\u0027Hour\u0027] \u003d \trow[\u0027Hour\u0027]\n\t\tdict[\u0027Sorted\u0027] \u003d row[\u0027Sorted\u0027]\n\t\tdict[\u0027DestinationInvalid\u0027] \u003d row[\u0027DestinationInvalid\u0027]\n\t\tdict[\u0027DestinationNone\u0027] \u003d row[\u0027DestinationNone\u0027]\n\t\tdict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t\tdict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t\tdict[\u0027Unexpected\u0027] \u003d row[\u0027Unexpected\u0027]\n\t\tdict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t\tdict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t\tdict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t\tdict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t\tdict[\u0027TrackingError\u0027] \u003d row[\u0027TrackingError\u0027]\n\t\tdict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t\tdict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t\tgraph.append(dict)\n\t\n\t\n\tself.getSibling(\"Hourly Sorter Details\").props.dataSources.example \u003d graph\n\tself.props.data \u003d system.dataset.toDataSet(headers,data)" + "script": "\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 1. Database Connection Check\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t # Check if MariaDB exists and is ready\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t \n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t \n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000) # Wait for connection attempt\n\t \n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {}\".format(maria_conn.status)\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t \n\t # Quick ping test (3 second timeout)\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t \n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {}\".format(str(e))\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 2. Original Logic (Protected)\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t path \u003d \"\"\n\t headers \u003d []\n\t graph \u003d []\n\t\n\t if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Sorter Details Count\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (#)\",\"Sorted (#)\",\"Dest Inv (#)\",\"Dest None (#)\",\"Dest Dis (#)\",\"Dest Full (#)\",\"Unexpected (#)\",\"Dest Fault (#)\",\"Div Fail (#)\",\"Gap Err (#)\",\"Lost (#)\",\"Track Err (#)\",\"Unknown (#)\",\"Unsafe (#)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Sorter Details Percent\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (%)\",\"Sorted (%)\",\"Dest Inv (%)\",\"Dest None (%)\",\"Dest Dis (%)\",\"Dest Full (%)\",\"Unexpected (%)\",\"Dest Fault (%)\",\"Div Fail (%)\",\"Gap Err (%)\",\"Lost (%)\",\"Track Err (%)\",\"Unknown (%)\",\"Unsafe (%)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Sorter Details Rate\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (pph)\",\"Sorted (pph)\",\"Dest Inv (pph)\",\"Dest None (pph)\",\"Dest Dis (pph)\",\"Dest Full (pph)\",\"Unexpected (pph)\",\"Dest Fault (pph)\",\"Div Fail (pph)\",\"Gap Err (pph)\",\"Lost (pph)\",\"Track Err (pph)\",\"Unknown (pph)\",\"Unsafe (pph)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t for row in data:\n\t dict \u003d {}\n\t dict[\u0027Hour\u0027] \u003d row[\u0027Hour\u0027]\n\t dict[\u0027Sorted\u0027] \u003d row[\u0027Sorted\u0027]\n\t dict[\u0027DestinationInvalid\u0027] \u003d row[\u0027DestinationInvalid\u0027]\n\t dict[\u0027DestinationNone\u0027] \u003d row[\u0027DestinationNone\u0027]\n\t dict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t dict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t dict[\u0027Unexpected\u0027] \u003d row[\u0027Unexpected\u0027]\n\t dict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t dict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t dict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t dict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t dict[\u0027TrackingError\u0027] \u003d row[\u0027TrackingError\u0027]\n\t dict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t dict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t graph.append(dict)\n\t\n\t self.getSibling(\"Hourly Sorter Details\").props.dataSources.example \u003d graph\n\t self.props.data \u003d system.dataset.toDataSet(headers, data)\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {}\".format(str(e))\n\t print(\"🚨 {}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" } }, "custom.time": { @@ -4933,120 +4813,15 @@ "$": [ "ds", 192, - 1747222130163 + 1747403903262 ], "$columns": [ { "data": [ - "2025-05-14 14:00" + "Database connection failed: \u0027com.inductiveautomation.ignition.common.BasicDataset\u0027 object is not iterable" ], - "name": "Start Timestamp", + "name": "Error", "type": "String" - }, - { - "data": [ - "H1" - ], - "name": "Hour", - "type": "String" - }, - { - "data": [ - 4 - ], - "name": "Inducted (#)", - "type": "Long" - }, - { - "data": [ - 4 - ], - "name": "Sorted (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Dest Inv (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Dest None (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Dest Dis (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Dest Full (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Unexpected (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Dest Fault (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Div Fail (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Gap Err (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Lost (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Track Err (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Unknown (#)", - "type": "Double" - }, - { - "data": [ - 0 - ], - "name": "Unsafe (#)", - "type": "Double" } ] }, @@ -5079,24 +4854,7 @@ }, "props": { "dataSources": { - "example": [ - { - "DestinationDisabled": 0, - "DestinationFault": 0, - "DestinationFull": 0, - "DestinationInvalid": 0, - "DestinationNone": 0, - "DivertFail": 0, - "GapError": 0, - "Hour": "H1", - "Lost": 0, - "Sorted": 4, - "TrackingError": 0, - "Unexpected": 0, - "Unknown": 0, - "Unsafe": 0 - } - ] + "example": [] }, "series": [ { @@ -8692,7 +8450,7 @@ }, "onChange": { "enabled": null, - "script": "\t\n\tpath \u003d \"\"\n\theaders \u003d []\n\tgraph \u003d []\n\t\n\tif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t\tpath \u003d \"Statistics/Hourly Lane Count\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Diverted (#)\",\"Dest Full (#)\",\"Dest Jam (#)\",\"Dest Disabled (#)\",\"Dest Fault (#)\",\"Divert Fail (#)\",\"Lost (#)\",\"Unsafe (#)\",\"Dim Err (#)\",\"Gap Err (#)\",\"Unknown (#)\"]\n\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (#): [bold]{valueY}[/]\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\t\t\n\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\telif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t\tpath \u003d \"Statistics/Hourly Lane Percent\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Diverted (%)\",\"Dest Full (%)\",\"Dest Jam (%)\",\"Dest Disabled (%)\",\"Dest Fault (%)\",\"Divert Fail (%)\",\"Lost (%)\",\"Unsafe (%)\",\"Dim Err (%)\",\"Gap Err (%)\",\"Unknown (%)\"]\n\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (%): [bold]{valueY}[/]%\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\t\t\n\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d 100\n\telse:\n\t\tpath \u003d \"Statistics/Hourly Lane Rate\"\n\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Diverted (pph)\",\"Dest Full (pph)\",\"Dest Jam (pph)\",\"Dest Disabled (pph)\",\"Dest Fault (pph)\",\"Divert Fail (pph)\",\"Lost (pph)\",\"Unsafe (pph)\",\"Dim Err (pph)\",\"Gap Err (pph)\",\"Unknown (pph)\"]\n\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (pph): [bold]{valueY}[/] pph\"\n\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/] pph\"\t\n\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\t\t\n\tparams \u003d {\"starttime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\"endtime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate,\"lane\":self.parent.parent.parent.getChild(\"Lane Drop Down\").getChild(\"Lane\").props.value}\t\n\t\n\tdata \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path,params))\n\t\n\tfor row in data:\t\n\t\tdict \u003d {}\n\t\tdict[\u0027Hour\u0027] \u003d \trow[\u0027Hour\u0027]\n\t\tdict[\u0027Diverted\u0027] \u003d row[\u0027Diverted\u0027]\n\t\tdict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t\tdict[\u0027DestinationJam\u0027] \u003d row[\u0027DestinationJam\u0027]\n\t\tdict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t\tdict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t\tdict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t\tdict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t\tdict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t\tdict[\u0027DimError\u0027] \u003d row[\u0027DimError\u0027]\n\t\tdict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t\tdict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t\tgraph.append(dict)\n\t\n\tself.getSibling(\"Hourly Lane\").props.dataSources.example \u003d graph\n\tself.props.data \u003d system.dataset.toDataSet(headers,data)" + "script": "\t\t\n\ttry:\n\t # Check if MariaDB exists and is ready\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t \n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t \n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000) # Wait for connection attempt\n\t \n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {}\".format(maria_conn.status)\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t \n\t # Quick ping test (3 second timeout)\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t \n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {}\".format(str(e))\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t\n\ttry:\n\t\t\n\t\n\t\tpath \u003d \"\"\n\t\theaders \u003d []\n\t\tgraph \u003d []\n\t\t\n\t\tif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t\t\tpath \u003d \"Statistics/Hourly Lane Count\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Diverted (#)\",\"Dest Full (#)\",\"Dest Jam (#)\",\"Dest Disabled (#)\",\"Dest Fault (#)\",\"Divert Fail (#)\",\"Lost (#)\",\"Unsafe (#)\",\"Dim Err (#)\",\"Gap Err (#)\",\"Unknown (#)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\t\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\t\telif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t\t\tpath \u003d \"Statistics/Hourly Lane Percent\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Diverted (%)\",\"Dest Full (%)\",\"Dest Jam (%)\",\"Dest Disabled (%)\",\"Dest Fault (%)\",\"Divert Fail (%)\",\"Lost (%)\",\"Unsafe (%)\",\"Dim Err (%)\",\"Gap Err (%)\",\"Unknown (%)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\t\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d 100\n\t\telse:\n\t\t\tpath \u003d \"Statistics/Hourly Lane Rate\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Diverted (pph)\",\"Dest Full (pph)\",\"Dest Jam (pph)\",\"Dest Disabled (pph)\",\"Dest Fault (pph)\",\"Divert Fail (pph)\",\"Lost (pph)\",\"Unsafe (pph)\",\"Dim Err (pph)\",\"Gap Err (pph)\",\"Unknown (pph)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/] pph\"\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\t\t\t\n\t\tparams \u003d {\"starttime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\"endtime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate,\"lane\":self.parent.parent.parent.getChild(\"Lane Drop Down\").getChild(\"Lane\").props.value}\t\n\t\t\n\t\tdata \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path,params))\n\t\t\n\t\tfor row in data:\t\n\t\t\tdict \u003d {}\n\t\t\tdict[\u0027Hour\u0027] \u003d \trow[\u0027Hour\u0027]\n\t\t\tdict[\u0027Diverted\u0027] \u003d row[\u0027Diverted\u0027]\n\t\t\tdict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t\t\tdict[\u0027DestinationJam\u0027] \u003d row[\u0027DestinationJam\u0027]\n\t\t\tdict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t\t\tdict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t\t\tdict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t\t\tdict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t\t\tdict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t\t\tdict[\u0027DimError\u0027] \u003d row[\u0027DimError\u0027]\n\t\t\tdict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t\t\tdict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t\t\tgraph.append(dict)\n\t\t\n\t\tself.getSibling(\"Hourly Lane\").props.dataSources.example \u003d graph\n\t\tself.props.data \u003d system.dataset.toDataSet(headers,data)\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {}\".format(str(e))\n\t print(\"🚨 {}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" } }, "custom.time": { @@ -9731,77 +9489,14 @@ "$": [ "ds", 192, - 1747222130597 + 1747403902267 ], "$columns": [ { - "data": [], - "name": "Start Timestamp", - "type": "String" - }, - { - "data": [], - "name": "Hour", - "type": "String" - }, - { - "data": [], - "name": "Total (#)", - "type": "String" - }, - { - "data": [], - "name": "Diverted (#)", - "type": "String" - }, - { - "data": [], - "name": "Dest Full (#)", - "type": "String" - }, - { - "data": [], - "name": "Dest Jam (#)", - "type": "String" - }, - { - "data": [], - "name": "Dest Disabled (#)", - "type": "String" - }, - { - "data": [], - "name": "Dest Fault (#)", - "type": "String" - }, - { - "data": [], - "name": "Divert Fail (#)", - "type": "String" - }, - { - "data": [], - "name": "Lost (#)", - "type": "String" - }, - { - "data": [], - "name": "Unsafe (#)", - "type": "String" - }, - { - "data": [], - "name": "Dim Err (#)", - "type": "String" - }, - { - "data": [], - "name": "Gap Err (#)", - "type": "String" - }, - { - "data": [], - "name": "Unknown (#)", + "data": [ + "Database connection failed: \u0027com.inductiveautomation.ignition.common.BasicDataset\u0027 object is not iterable" + ], + "name": "Error", "type": "String" } ] @@ -25064,29 +24759,23 @@ "$": [ "ds", 192, - 1747222089785 + 1747403052050 ], "$columns": [ { - "data": [ - "S011921" - ], + "data": [], "name": "Lane", "type": "String" }, { - "data": [ - 4 - ], + "data": [], "name": "Total (#)", - "type": "Long" + "type": "String" }, { - "data": [ - 0 - ], + "data": [], "name": "DestFull (#)", - "type": "Double" + "type": "String" } ] }, @@ -25118,13 +24807,7 @@ }, "props": { "dataSources": { - "example": [ - { - "DestFull_count": 0, - "Lane": "S011921", - "Total_count": 4 - } - ] + "example": [] }, "series": [ { @@ -27849,41 +27532,31 @@ "$": [ "ds", 192, - 1747222089785 + 1747403052050 ], "$columns": [ { - "data": [ - "2025-05-14 14:00" - ], + "data": [], "name": "Start Timestamp", "type": "String" }, { - "data": [ - "H1" - ], + "data": [], "name": "Hour", "type": "String" }, { - "data": [ - null - ], + "data": [], "name": "Cycles of ULGL1", "type": "String" }, { - "data": [ - null - ], + "data": [], "name": "Cycles of ULGL2", "type": "String" }, { - "data": [ - null - ], + "data": [], "name": "Cycles of ULGL3", "type": "String" } @@ -27919,14 +27592,7 @@ }, "props": { "dataSources": { - "example": [ - { - "Hour": "H1", - "ULGL1": null, - "ULGL2": null, - "ULGL3": null - } - ] + "example": [] }, "legend": { "enabled": false @@ -28921,7 +28587,7 @@ } }, "props": { - "currentTabIndex": 11, + "currentTabIndex": 9, "menuStyle": { "backgroundColor": "#FFFFFFBD" }, @@ -31430,7 +31096,7 @@ }, "props": { "dismissOnSelect": false, - "formattedValue": "May 14, 2025 2:28 PM", + "formattedValue": "May 16, 2025 4:58 PM", "formattedValues": { "date": "Mar 26, 2021", "datetime": "Mar 26, 2021 12:00 AM", @@ -31440,9 +31106,9 @@ "$": [ "ts", 192, - 1747222129890 + 1747403903262 ], - "$ts": 1747218529000 + "$ts": 1747400303000 } }, "type": "ia.input.date-time-input" @@ -31590,7 +31256,7 @@ }, "props": { "dismissOnSelect": false, - "formattedValue": "May 14, 2025 3:28 PM", + "formattedValue": "May 16, 2025 5:58 PM", "formattedValues": { "date": "Mar 29, 2021", "datetime": "Mar 29, 2021 1:37 PM", @@ -31600,9 +31266,9 @@ "$": [ "ts", 192, - 1747222129890 + 1747403903257 ], - "$ts": 1747222129000 + "$ts": 1747403903000 } }, "type": "ia.input.date-time-input" From f2faa4e57bdf2a33450d9e5c60430ea198cb3a59 Mon Sep 17 00:00:00 2001 From: gugak <107577102+Salijoghli@users.noreply.github.com> Date: Mon, 19 May 2025 18:26:20 +0400 Subject: [PATCH 2/6] fixed database error, errors are gone and data is updated when connection is restored --- .../views/Windows/Statistics/view.json | 278 +++++++++++++++--- 1 file changed, 239 insertions(+), 39 deletions(-) diff --git a/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json b/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json index dbcd735..849d607 100644 --- a/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json +++ b/MTN6_SCADA/com.inductiveautomation.perspective/views/Windows/Statistics/view.json @@ -55,7 +55,7 @@ }, "onChange": { "enabled": null, - "script": "\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n # 1. Database Connection Check\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n try:\n # Check if MariaDB exists and is ready\n connections \u003d system.db.getConnections()\n maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n \n if not maria_conn:\n print(\"❌ MariaDB connection not configured in Ignition\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n return\n \n if maria_conn.status \u003d\u003d \"Disabled\":\n print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n system.db.enableConnection(\"MariaDB\")\n system.util.sleep(2000) # Wait for connection attempt\n \n if maria_conn.status !\u003d \"Connected\":\n error_msg \u003d \"MariaDB status: {maria_conn.status}\"\n print(\"{error_msg}\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n return\n \n # Quick ping test (3 second timeout)\n if not system.db.ping(\"MariaDB\", 3000):\n raise Exception(\"Database not responding to ping\")\n \n except Exception as e:\n error_msg \u003d \"Database connection failed: {str(e)}\"\n print(\"{error_msg}\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n return\n\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n # 2. Original Logic (Protected)\n # \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n try:\n path \u003d \"\"\n headers \u003d []\n graph \u003d []\n \n # Mode selection (unchanged)\n if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n path \u003d \"Statistics/Hourly Scanner Count\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Good Read (#)\",\"No Read (#)\",\"Multi Read (#)\",\"No Code (#)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n path \u003d \"Statistics/Hourly Scanner Percent\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Good Read (%)\",\"No Read (%)\",\"Multi Read (%)\",\"No Code (%)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d 100\n else:\n path \u003d \"Statistics/Hourly Scanner Rate\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Good Read (pph)\",\"No Read (pph)\",\"Multi Read (pph)\",\"No Code (pph)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (pph): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n \n params \u003d {\n \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n }\n \n # Execute query with error handling\n data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n \n # Process data (unchanged)\n graph \u003d []\n for row in data: \n dict \u003d {\n \u0027Hour\u0027: row[\u0027Hour\u0027],\n \u0027GoodRead\u0027: row[\u0027GoodRead\u0027],\n \u0027NoRead\u0027: row[\u0027NoRead\u0027],\n \u0027MultiRead\u0027: row[\u0027MultiRead\u0027],\n \u0027NoCode\u0027: row[\u0027NoCode\u0027]\n }\n graph.append(dict)\n \n # Update components\n self.getSibling(\"Hourly Scanner\").props.dataSources.example \u003d graph\n self.props.data \u003d system.dataset.toDataSet(headers, data)\n \n except Exception as e:\n error_msg \u003d \"Query failed: {str(e)}\"\n print(\"🚨 {error_msg}\")\n self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" + "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if(system.tag.readBlocking([db_tag_path])[0].value):\n path \u003d \"\"\n headers \u003d []\n graph \u003d []\n \n # Mode selection (unchanged)\n if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n path \u003d \"Statistics/Hourly Scanner Count\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Good Read (#)\",\"No Read (#)\",\"Multi Read (#)\",\"No Code (#)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (#): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n path \u003d \"Statistics/Hourly Scanner Percent\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Good Read (%)\",\"No Read (%)\",\"Multi Read (%)\",\"No Code (%)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (%): [bold]{valueY}[/]%\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d 100\n else:\n path \u003d \"Statistics/Hourly Scanner Rate\"\n headers \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Good Read (pph)\",\"No Read (pph)\",\"Multi Read (pph)\",\"No Code (pph)\"]\n self.getSibling(\"Hourly Scanner\").props.series[0].tooltip.text \u003d \"Good Read (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.series[1].tooltip.text \u003d \"No Read (pph): [bold]{valueY}[/]\"\n self.getSibling(\"Hourly Scanner\").props.series[2].tooltip.text \u003d \"Multi Read (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.series[3].tooltip.text \u003d \"No Code (pph): [bold]{valueY}[/] pph\"\n self.getSibling(\"Hourly Scanner\").props.yAxes[0].value.range.max \u003d \"\"\n \n params \u003d {\n \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n }\n \n # Execute query with error handling\n data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n \n # Process data (unchanged)\n graph \u003d []\n for row in data: \n dict \u003d {\n \u0027Hour\u0027: row[\u0027Hour\u0027],\n \u0027GoodRead\u0027: row[\u0027GoodRead\u0027],\n \u0027NoRead\u0027: row[\u0027NoRead\u0027],\n \u0027MultiRead\u0027: row[\u0027MultiRead\u0027],\n \u0027NoCode\u0027: row[\u0027NoCode\u0027]\n }\n graph.append(dict)\n \n # Update components\n self.getSibling(\"Hourly Scanner\").props.dataSources.example \u003d graph\n self.props.data \u003d system.dataset.toDataSet(headers, data)\n " } }, "custom.time": { @@ -618,14 +618,42 @@ "$": [ "ds", 192, - 1747403903257 + 1747664567746 ], "$columns": [ { - "data": [ - "Database connection failed: {str(e)}" - ], - "name": "Error", + "data": [], + "name": "Start Timestamp", + "type": "String" + }, + { + "data": [], + "name": "Hour", + "type": "String" + }, + { + "data": [], + "name": "Total (#)", + "type": "String" + }, + { + "data": [], + "name": "Good Read (#)", + "type": "String" + }, + { + "data": [], + "name": "No Read (#)", + "type": "String" + }, + { + "data": [], + "name": "Multi Read (#)", + "type": "String" + }, + { + "data": [], + "name": "No Code (#)", "type": "String" } ] @@ -1911,7 +1939,7 @@ }, "onChange": { "enabled": null, - "script": "\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 1. Database Connection Check\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t # Check if MariaDB exists and is ready\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t \n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t \n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000) # Wait for connection attempt\n\t \n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {}\".format(maria_conn.status)\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t \n\t # Quick ping test (3 second timeout)\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t \n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {}\".format(str(e))\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 2. Original Logic (Wrapped in Try Block)\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t path \u003d \"\"\n\t headers \u003d []\n\t rows \u003d []\n\t\n\t # Define the appropriate path based on the selected aggregation mode\n\t mode \u003d self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value\n\t if mode \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Induct Count\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (#)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif mode \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Induct Percent\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (%)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Induct Rate\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (pph)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t # Fetch the data from the database\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate, \n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t # Prepare the rows for the dataset without \u0027SingleCarrier\u0027 and \u0027DoubleCarrier\u0027\n\t for row in data:\n\t row_data \u003d [row[\u0027StartTimestamp\u0027], row[\u0027Hour\u0027]]\n\t \n\t if mode \u003d\u003d \"Count\":\n\t row_data.append(row[\u0027Total_count\u0027])\n\t elif mode \u003d\u003d \"Percentage\":\n\t row_data.append(row[\u0027Total_percentage\u0027])\n\t else:\n\t row_data.append(row[\u0027Total_pph\u0027])\n\t\n\t if \u0027SingleCarrier\u0027 in row and row[\u0027SingleCarrier\u0027] is not None:\n\t row_data.append(row[\u0027SingleCarrier\u0027])\n\t headers.append(\"Single Carrier (#)\")\n\t if \u0027DoubleCarrier\u0027 in row and row[\u0027DoubleCarrier\u0027] is not None:\n\t row_data.append(row[\u0027DoubleCarrier\u0027])\n\t headers.append(\"Double Carrier (#)\")\n\t\n\t rows.append(row_data)\n\t\n\t # Filter headers and rows to exclude unwanted columns\n\t filtered_headers \u003d [h for h in headers if h not in [\"Single Carrier (#)\", \"Double Carrier (#)\"]]\n\t filtered_rows \u003d [\n\t [val for idx, val in enumerate(r) if headers[idx] not in [\"Single Carrier (#)\", \"Double Carrier (#)\"]]\n\t for r in rows\n\t ]\n\t\n\t dataset \u003d system.dataset.toDataSet(filtered_headers, filtered_rows)\n\t\n\t # Update the dataset and chart data\n\t self.props.data \u003d dataset\n\t self.getSibling(\"Hourly Induct\").props.dataSources.example \u003d dataset\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {}\".format(str(e))\n\t print(\"🚨 {}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" + "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if(system.tag.readBlocking([db_tag_path])[0].value):\n\t path \u003d \"\"\n\t headers \u003d []\n\t rows \u003d []\n\t\n\t # Define the appropriate path based on the selected aggregation mode\n\t mode \u003d self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value\n\t if mode \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Induct Count\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (#)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif mode \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Induct Percent\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (%)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Induct Rate\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Total (pph)\"]\n\t self.getSibling(\"Hourly Induct\").props.series[0].tooltip.text \u003d \"Total (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Induct\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t # Fetch the data from the database\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate, \n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t # Prepare the rows for the dataset without \u0027SingleCarrier\u0027 and \u0027DoubleCarrier\u0027\n\t for row in data:\n\t row_data \u003d [row[\u0027StartTimestamp\u0027], row[\u0027Hour\u0027]]\n\t \n\t if mode \u003d\u003d \"Count\":\n\t row_data.append(row[\u0027Total_count\u0027])\n\t elif mode \u003d\u003d \"Percentage\":\n\t row_data.append(row[\u0027Total_percentage\u0027])\n\t else:\n\t row_data.append(row[\u0027Total_pph\u0027])\n\t\n\t if \u0027SingleCarrier\u0027 in row and row[\u0027SingleCarrier\u0027] is not None:\n\t row_data.append(row[\u0027SingleCarrier\u0027])\n\t headers.append(\"Single Carrier (#)\")\n\t if \u0027DoubleCarrier\u0027 in row and row[\u0027DoubleCarrier\u0027] is not None:\n\t row_data.append(row[\u0027DoubleCarrier\u0027])\n\t headers.append(\"Double Carrier (#)\")\n\t\n\t rows.append(row_data)\n\t\n\t # Filter headers and rows to exclude unwanted columns\n\t filtered_headers \u003d [h for h in headers if h not in [\"Single Carrier (#)\", \"Double Carrier (#)\"]]\n\t filtered_rows \u003d [\n\t [val for idx, val in enumerate(r) if headers[idx] not in [\"Single Carrier (#)\", \"Double Carrier (#)\"]]\n\t for r in rows\n\t ]\n\t\n\t dataset \u003d system.dataset.toDataSet(filtered_headers, filtered_rows)\n\t\n\t # Update the dataset and chart data\n\t self.props.data \u003d dataset\n\t self.getSibling(\"Hourly Induct\").props.dataSources.example \u003d dataset\n\t" } }, "custom.time": { @@ -2134,14 +2162,22 @@ "$": [ "ds", 192, - 1747403903262 + 1747664567758 ], "$columns": [ { - "data": [ - "Database connection failed: \u0027com.inductiveautomation.ignition.common.BasicDataset\u0027 object is not iterable" - ], - "name": "Error", + "data": [], + "name": "Start Timestamp", + "type": "String" + }, + { + "data": [], + "name": "Hour", + "type": "String" + }, + { + "data": [], + "name": "Total (#)", "type": "String" } ] @@ -2178,7 +2214,7 @@ "$": [ "ds", 192, - 1747402203240 + 1747664567758 ], "$columns": [ { @@ -2936,7 +2972,7 @@ }, "onChange": { "enabled": null, - "script": "\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 1. Database Connection Check\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t\n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t\n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000)\n\t\n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {maria_conn.status}\"\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {str(e)}\"\n\t print(error_msg)\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 2. Original Logic\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t path \u003d \"\"\n\t headers \u003d []\n\t graph \u003d []\n\t\n\t mode \u003d self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value\n\t\n\t if mode \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Sorter Summary Count\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (#)\", \"Sorted (#)\", \"Awcs Recirc (#)\", \"Operational Recirc (#)\", \"Machine Recirc (#)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif mode \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Sorter Summary Percent\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (%)\", \"Sorted (%)\", \"Awcs Recirc (%)\", \"Operational Recirc (%)\", \"Machine Recirc (%)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Sorter Summary Rate\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (pph)\", \"Sorted (pph)\", \"Awcs Recirc (pph)\", \"Operational Recirc (pph)\", \"Machine Recirc (pph)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t for row in data:\n\t entry \u003d {\n\t \"Hour\": row[\"Hour\"],\n\t \"Sorted\": row[\"Sorted\"],\n\t \"AwcsRecirc\": row[\"AwcsRecirc\"],\n\t \"OperationalRecirc\": row[\"OperationalRecirc\"],\n\t \"MachineRecirc\": row[\"MachineRecirc\"]\n\t }\n\t graph.append(entry)\n\t\n\t self.getSibling(\"Hourly Sorter Summary\").props.dataSources.example \u003d graph\n\t self.props.data \u003d system.dataset.toDataSet(headers, data)\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {str(e)}\"\n\t print(\"🚨 {error_msg}\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" + "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if(system.tag.readBlocking([db_tag_path])[0].value):\n\t path \u003d \"\"\n\t headers \u003d []\n\t graph \u003d []\n\t\n\t mode \u003d self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value\n\t\n\t if mode \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Sorter Summary Count\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (#)\", \"Sorted (#)\", \"Awcs Recirc (#)\", \"Operational Recirc (#)\", \"Machine Recirc (#)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif mode \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Sorter Summary Percent\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (%)\", \"Sorted (%)\", \"Awcs Recirc (%)\", \"Operational Recirc (%)\", \"Machine Recirc (%)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Sorter Summary Rate\"\n\t headers \u003d [\"Start Timestamp\", \"Hour\", \"Inducted (pph)\", \"Sorted (pph)\", \"Awcs Recirc (pph)\", \"Operational Recirc (pph)\", \"Machine Recirc (pph)\"]\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[1].tooltip.text \u003d \"Awcs Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[2].tooltip.text \u003d \"Operational Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.series[3].tooltip.text \u003d \"Machine Recirc (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Summary\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t for row in data:\n\t entry \u003d {\n\t \"Hour\": row[\"Hour\"],\n\t \"Sorted\": row[\"Sorted\"],\n\t \"AwcsRecirc\": row[\"AwcsRecirc\"],\n\t \"OperationalRecirc\": row[\"OperationalRecirc\"],\n\t \"MachineRecirc\": row[\"MachineRecirc\"]\n\t }\n\t graph.append(entry)\n\t\n\t self.getSibling(\"Hourly Sorter Summary\").props.dataSources.example \u003d graph\n\t self.props.data \u003d system.dataset.toDataSet(headers, data)\n" } }, "custom.time": { @@ -3499,14 +3535,42 @@ "$": [ "ds", 192, - 1747403903257 + 1747664567746 ], "$columns": [ { - "data": [ - "Database connection failed: {str(e)}" - ], - "name": "Error", + "data": [], + "name": "Start Timestamp", + "type": "String" + }, + { + "data": [], + "name": "Hour", + "type": "String" + }, + { + "data": [], + "name": "Inducted (#)", + "type": "String" + }, + { + "data": [], + "name": "Sorted (#)", + "type": "String" + }, + { + "data": [], + "name": "Awcs Recirc (#)", + "type": "String" + }, + { + "data": [], + "name": "Operational Recirc (#)", + "type": "String" + }, + { + "data": [], + "name": "Machine Recirc (#)", "type": "String" } ] @@ -4791,7 +4855,7 @@ }, "onChange": { "enabled": null, - "script": "\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 1. Database Connection Check\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t # Check if MariaDB exists and is ready\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t \n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t \n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000) # Wait for connection attempt\n\t \n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {}\".format(maria_conn.status)\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t \n\t # Quick ping test (3 second timeout)\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t \n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {}\".format(str(e))\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\t# 2. Original Logic (Protected)\n\t# \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ttry:\n\t path \u003d \"\"\n\t headers \u003d []\n\t graph \u003d []\n\t\n\t if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Sorter Details Count\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (#)\",\"Sorted (#)\",\"Dest Inv (#)\",\"Dest None (#)\",\"Dest Dis (#)\",\"Dest Full (#)\",\"Unexpected (#)\",\"Dest Fault (#)\",\"Div Fail (#)\",\"Gap Err (#)\",\"Lost (#)\",\"Track Err (#)\",\"Unknown (#)\",\"Unsafe (#)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Sorter Details Percent\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (%)\",\"Sorted (%)\",\"Dest Inv (%)\",\"Dest None (%)\",\"Dest Dis (%)\",\"Dest Full (%)\",\"Unexpected (%)\",\"Dest Fault (%)\",\"Div Fail (%)\",\"Gap Err (%)\",\"Lost (%)\",\"Track Err (%)\",\"Unknown (%)\",\"Unsafe (%)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Sorter Details Rate\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (pph)\",\"Sorted (pph)\",\"Dest Inv (pph)\",\"Dest None (pph)\",\"Dest Dis (pph)\",\"Dest Full (pph)\",\"Unexpected (pph)\",\"Dest Fault (pph)\",\"Div Fail (pph)\",\"Gap Err (pph)\",\"Lost (pph)\",\"Track Err (pph)\",\"Unknown (pph)\",\"Unsafe (pph)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t for row in data:\n\t dict \u003d {}\n\t dict[\u0027Hour\u0027] \u003d row[\u0027Hour\u0027]\n\t dict[\u0027Sorted\u0027] \u003d row[\u0027Sorted\u0027]\n\t dict[\u0027DestinationInvalid\u0027] \u003d row[\u0027DestinationInvalid\u0027]\n\t dict[\u0027DestinationNone\u0027] \u003d row[\u0027DestinationNone\u0027]\n\t dict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t dict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t dict[\u0027Unexpected\u0027] \u003d row[\u0027Unexpected\u0027]\n\t dict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t dict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t dict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t dict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t dict[\u0027TrackingError\u0027] \u003d row[\u0027TrackingError\u0027]\n\t dict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t dict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t graph.append(dict)\n\t\n\t self.getSibling(\"Hourly Sorter Details\").props.dataSources.example \u003d graph\n\t self.props.data \u003d system.dataset.toDataSet(headers, data)\n\t\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {}\".format(str(e))\n\t print(\"🚨 {}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" + "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if(system.tag.readBlocking([db_tag_path])[0].value):\n\t path \u003d \"\"\n\t headers \u003d []\n\t graph \u003d []\n\t\n\t if self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t path \u003d \"Statistics/Hourly Sorter Details Count\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (#)\",\"Sorted (#)\",\"Dest Inv (#)\",\"Dest None (#)\",\"Dest Dis (#)\",\"Dest Full (#)\",\"Unexpected (#)\",\"Dest Fault (#)\",\"Div Fail (#)\",\"Gap Err (#)\",\"Lost (#)\",\"Track Err (#)\",\"Unknown (#)\",\"Unsafe (#)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\t elif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t path \u003d \"Statistics/Hourly Sorter Details Percent\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (%)\",\"Sorted (%)\",\"Dest Inv (%)\",\"Dest None (%)\",\"Dest Dis (%)\",\"Dest Full (%)\",\"Unexpected (%)\",\"Dest Fault (%)\",\"Div Fail (%)\",\"Gap Err (%)\",\"Lost (%)\",\"Track Err (%)\",\"Unknown (%)\",\"Unsafe (%)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d 100\n\t else:\n\t path \u003d \"Statistics/Hourly Sorter Details Rate\"\n\t headers \u003d [\"Start Timestamp\",\"Hour\",\"Inducted (pph)\",\"Sorted (pph)\",\"Dest Inv (pph)\",\"Dest None (pph)\",\"Dest Dis (pph)\",\"Dest Full (pph)\",\"Unexpected (pph)\",\"Dest Fault (pph)\",\"Div Fail (pph)\",\"Gap Err (pph)\",\"Lost (pph)\",\"Track Err (pph)\",\"Unknown (pph)\",\"Unsafe (pph)\"]\n\t self.getSibling(\"Hourly Sorter Details\").props.series[0].tooltip.text \u003d \"Sorted (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[1].tooltip.text \u003d \"Dest Invalid (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[2].tooltip.text \u003d \"Dest None (pph): [bold]{valueY}[/] pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[4].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[5].tooltip.text \u003d \"Unexpected (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[6].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[7].tooltip.text \u003d \"Div Fail (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[8].tooltip.text \u003d \"Gap Error (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[9].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[10].tooltip.text \u003d \"Tracking Err (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[11].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.series[12].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/]pph\"\n\t self.getSibling(\"Hourly Sorter Details\").props.yAxes[0].value.range.max \u003d \"\"\n\t\n\t params \u003d {\n\t \"starttime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\n\t \"endtime\": self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate\n\t }\n\t\n\t data \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path, params))\n\t\n\t for row in data:\n\t dict \u003d {}\n\t dict[\u0027Hour\u0027] \u003d row[\u0027Hour\u0027]\n\t dict[\u0027Sorted\u0027] \u003d row[\u0027Sorted\u0027]\n\t dict[\u0027DestinationInvalid\u0027] \u003d row[\u0027DestinationInvalid\u0027]\n\t dict[\u0027DestinationNone\u0027] \u003d row[\u0027DestinationNone\u0027]\n\t dict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t dict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t dict[\u0027Unexpected\u0027] \u003d row[\u0027Unexpected\u0027]\n\t dict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t dict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t dict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t dict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t dict[\u0027TrackingError\u0027] \u003d row[\u0027TrackingError\u0027]\n\t dict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t dict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t graph.append(dict)\n\t\n\t self.getSibling(\"Hourly Sorter Details\").props.dataSources.example \u003d graph\n\t self.props.data \u003d system.dataset.toDataSet(headers, data)\n\t" } }, "custom.time": { @@ -4813,14 +4877,87 @@ "$": [ "ds", 192, - 1747403903262 + 1747664567761 ], "$columns": [ { - "data": [ - "Database connection failed: \u0027com.inductiveautomation.ignition.common.BasicDataset\u0027 object is not iterable" - ], - "name": "Error", + "data": [], + "name": "Start Timestamp", + "type": "String" + }, + { + "data": [], + "name": "Hour", + "type": "String" + }, + { + "data": [], + "name": "Inducted (#)", + "type": "String" + }, + { + "data": [], + "name": "Sorted (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Inv (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest None (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Dis (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Full (#)", + "type": "String" + }, + { + "data": [], + "name": "Unexpected (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Fault (#)", + "type": "String" + }, + { + "data": [], + "name": "Div Fail (#)", + "type": "String" + }, + { + "data": [], + "name": "Gap Err (#)", + "type": "String" + }, + { + "data": [], + "name": "Lost (#)", + "type": "String" + }, + { + "data": [], + "name": "Track Err (#)", + "type": "String" + }, + { + "data": [], + "name": "Unknown (#)", + "type": "String" + }, + { + "data": [], + "name": "Unsafe (#)", "type": "String" } ] @@ -8450,7 +8587,7 @@ }, "onChange": { "enabled": null, - "script": "\t\t\n\ttry:\n\t # Check if MariaDB exists and is ready\n\t connections \u003d system.db.getConnections()\n\t maria_conn \u003d next((c for c in connections if c.name \u003d\u003d \"MariaDB\"), None)\n\t \n\t if not maria_conn:\n\t print(\"❌ MariaDB connection not configured in Ignition\")\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[\"Database connection \u0027MariaDB\u0027 not found\"]])\n\t return\n\t \n\t if maria_conn.status \u003d\u003d \"Disabled\":\n\t print(\"⚠️ MariaDB is disabled - attempting to enable...\")\n\t system.db.enableConnection(\"MariaDB\")\n\t system.util.sleep(2000) # Wait for connection attempt\n\t \n\t if maria_conn.status !\u003d \"Connected\":\n\t error_msg \u003d \"MariaDB status: {}\".format(maria_conn.status)\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t \n\t # Quick ping test (3 second timeout)\n\t if not system.db.ping(\"MariaDB\", 3000):\n\t raise Exception(\"Database not responding to ping\")\n\t \n\texcept Exception as e:\n\t error_msg \u003d \"Database connection failed: {}\".format(str(e))\n\t print(\"{}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])\n\t return\n\t\n\t\n\ttry:\n\t\t\n\t\n\t\tpath \u003d \"\"\n\t\theaders \u003d []\n\t\tgraph \u003d []\n\t\t\n\t\tif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t\t\tpath \u003d \"Statistics/Hourly Lane Count\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Diverted (#)\",\"Dest Full (#)\",\"Dest Jam (#)\",\"Dest Disabled (#)\",\"Dest Fault (#)\",\"Divert Fail (#)\",\"Lost (#)\",\"Unsafe (#)\",\"Dim Err (#)\",\"Gap Err (#)\",\"Unknown (#)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\t\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\t\telif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t\t\tpath \u003d \"Statistics/Hourly Lane Percent\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Diverted (%)\",\"Dest Full (%)\",\"Dest Jam (%)\",\"Dest Disabled (%)\",\"Dest Fault (%)\",\"Divert Fail (%)\",\"Lost (%)\",\"Unsafe (%)\",\"Dim Err (%)\",\"Gap Err (%)\",\"Unknown (%)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\t\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d 100\n\t\telse:\n\t\t\tpath \u003d \"Statistics/Hourly Lane Rate\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Diverted (pph)\",\"Dest Full (pph)\",\"Dest Jam (pph)\",\"Dest Disabled (pph)\",\"Dest Fault (pph)\",\"Divert Fail (pph)\",\"Lost (pph)\",\"Unsafe (pph)\",\"Dim Err (pph)\",\"Gap Err (pph)\",\"Unknown (pph)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/] pph\"\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\t\t\t\n\t\tparams \u003d {\"starttime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\"endtime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate,\"lane\":self.parent.parent.parent.getChild(\"Lane Drop Down\").getChild(\"Lane\").props.value}\t\n\t\t\n\t\tdata \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path,params))\n\t\t\n\t\tfor row in data:\t\n\t\t\tdict \u003d {}\n\t\t\tdict[\u0027Hour\u0027] \u003d \trow[\u0027Hour\u0027]\n\t\t\tdict[\u0027Diverted\u0027] \u003d row[\u0027Diverted\u0027]\n\t\t\tdict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t\t\tdict[\u0027DestinationJam\u0027] \u003d row[\u0027DestinationJam\u0027]\n\t\t\tdict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t\t\tdict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t\t\tdict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t\t\tdict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t\t\tdict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t\t\tdict[\u0027DimError\u0027] \u003d row[\u0027DimError\u0027]\n\t\t\tdict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t\t\tdict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t\t\tgraph.append(dict)\n\t\t\n\t\tself.getSibling(\"Hourly Lane\").props.dataSources.example \u003d graph\n\t\tself.props.data \u003d system.dataset.toDataSet(headers,data)\n\texcept Exception as e:\n\t error_msg \u003d \"Query failed: {}\".format(str(e))\n\t print(\"🚨 {}\".format(error_msg))\n\t self.props.data \u003d system.dataset.toDataSet([\"Error\"], [[error_msg]])" + "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if(system.tag.readBlocking([db_tag_path])[0].value):\n\t\tpath \u003d \"\"\n\t\theaders \u003d []\n\t\tgraph \u003d []\n\t\t\n\t\tif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Count\":\n\t\t\tpath \u003d \"Statistics/Hourly Lane Count\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (#)\",\"Diverted (#)\",\"Dest Full (#)\",\"Dest Jam (#)\",\"Dest Disabled (#)\",\"Dest Fault (#)\",\"Divert Fail (#)\",\"Lost (#)\",\"Unsafe (#)\",\"Dim Err (#)\",\"Gap Err (#)\",\"Unknown (#)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (#): [bold]{valueY}[/]\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (#): [bold]{valueY}[/]\"\t\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\t\telif self.parent.parent.parent.getChild(\"Aggregation_Mode\").getChild(\"Dropdown_Aggregation_mode\").props.value \u003d\u003d \"Percentage\":\n\t\t\tpath \u003d \"Statistics/Hourly Lane Percent\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (%)\",\"Diverted (%)\",\"Dest Full (%)\",\"Dest Jam (%)\",\"Dest Disabled (%)\",\"Dest Fault (%)\",\"Divert Fail (%)\",\"Lost (%)\",\"Unsafe (%)\",\"Dim Err (%)\",\"Gap Err (%)\",\"Unknown (%)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (%): [bold]{valueY}[/]%\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (%): [bold]{valueY}[/]%\"\t\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d 100\n\t\telse:\n\t\t\tpath \u003d \"Statistics/Hourly Lane Rate\"\n\t\t\theaders \u003d [\"Start Timestamp\",\"Hour\",\"Total (pph)\",\"Diverted (pph)\",\"Dest Full (pph)\",\"Dest Jam (pph)\",\"Dest Disabled (pph)\",\"Dest Fault (pph)\",\"Divert Fail (pph)\",\"Lost (pph)\",\"Unsafe (pph)\",\"Dim Err (pph)\",\"Gap Err (pph)\",\"Unknown (pph)\"]\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[0].tooltip.text \u003d \"Diverted (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[1].tooltip.text \u003d \"Dest Full (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[2].tooltip.text \u003d \"Dest Jam (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[3].tooltip.text \u003d \"Dest Disabled (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[4].tooltip.text \u003d \"Dest Fault (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[5].tooltip.text \u003d \"Divert Fail (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[6].tooltip.text \u003d \"Lost (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[7].tooltip.text \u003d \"Unsafe (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[8].tooltip.text \u003d \"Dim Err (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[9].tooltip.text \u003d \"Gap Err (pph): [bold]{valueY}[/] pph\"\n\t\t\tself.getSibling(\"Hourly Lane\").props.series[10].tooltip.text \u003d \"Unknown (pph): [bold]{valueY}[/] pph\"\t\n\t\t\tself.getSibling(\"Hourly Lane\").props.yAxes[0].value.range.max \u003d \"\"\n\t\t\t\n\t\tparams \u003d {\"starttime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.StartDate,\"endtime\":self.parent.parent.parent.getChild(\"Period_not_Global_0\").custom.EndDate,\"lane\":self.parent.parent.parent.getChild(\"Lane Drop Down\").getChild(\"Lane\").props.value}\t\n\t\t\n\t\tdata \u003d system.dataset.toPyDataSet(system.db.runNamedQuery(path,params))\n\t\t\n\t\tfor row in data:\t\n\t\t\tdict \u003d {}\n\t\t\tdict[\u0027Hour\u0027] \u003d \trow[\u0027Hour\u0027]\n\t\t\tdict[\u0027Diverted\u0027] \u003d row[\u0027Diverted\u0027]\n\t\t\tdict[\u0027DestinationFull\u0027] \u003d row[\u0027DestinationFull\u0027]\n\t\t\tdict[\u0027DestinationJam\u0027] \u003d row[\u0027DestinationJam\u0027]\n\t\t\tdict[\u0027DestinationDisabled\u0027] \u003d row[\u0027DestinationDisabled\u0027]\n\t\t\tdict[\u0027DestinationFault\u0027] \u003d row[\u0027DestinationFault\u0027]\n\t\t\tdict[\u0027DivertFail\u0027] \u003d row[\u0027DivertFail\u0027]\n\t\t\tdict[\u0027Lost\u0027] \u003d row[\u0027Lost\u0027]\n\t\t\tdict[\u0027Unsafe\u0027] \u003d row[\u0027Unsafe\u0027]\n\t\t\tdict[\u0027DimError\u0027] \u003d row[\u0027DimError\u0027]\n\t\t\tdict[\u0027GapError\u0027] \u003d row[\u0027GapError\u0027]\n\t\t\tdict[\u0027Unknown\u0027] \u003d row[\u0027Unknown\u0027]\n\t\t\tgraph.append(dict)\n\t\t\n\t\tself.getSibling(\"Hourly Lane\").props.dataSources.example \u003d graph\n\t\tself.props.data \u003d system.dataset.toDataSet(headers,data)\n" } }, "custom.time": { @@ -9489,14 +9626,77 @@ "$": [ "ds", 192, - 1747403902267 + 1747664567761 ], "$columns": [ { - "data": [ - "Database connection failed: \u0027com.inductiveautomation.ignition.common.BasicDataset\u0027 object is not iterable" - ], - "name": "Error", + "data": [], + "name": "Start Timestamp", + "type": "String" + }, + { + "data": [], + "name": "Hour", + "type": "String" + }, + { + "data": [], + "name": "Total (#)", + "type": "String" + }, + { + "data": [], + "name": "Diverted (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Full (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Jam (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Disabled (#)", + "type": "String" + }, + { + "data": [], + "name": "Dest Fault (#)", + "type": "String" + }, + { + "data": [], + "name": "Divert Fail (#)", + "type": "String" + }, + { + "data": [], + "name": "Lost (#)", + "type": "String" + }, + { + "data": [], + "name": "Unsafe (#)", + "type": "String" + }, + { + "data": [], + "name": "Dim Err (#)", + "type": "String" + }, + { + "data": [], + "name": "Gap Err (#)", + "type": "String" + }, + { + "data": [], + "name": "Unknown (#)", "type": "String" } ] @@ -24759,7 +24959,7 @@ "$": [ "ds", 192, - 1747403052050 + 1747664538963 ], "$columns": [ { @@ -27532,7 +27732,7 @@ "$": [ "ds", 192, - 1747403052050 + 1747664538963 ], "$columns": [ { @@ -31096,7 +31296,7 @@ }, "props": { "dismissOnSelect": false, - "formattedValue": "May 16, 2025 4:58 PM", + "formattedValue": "May 19, 2025 5:22 PM", "formattedValues": { "date": "Mar 26, 2021", "datetime": "Mar 26, 2021 12:00 AM", @@ -31106,9 +31306,9 @@ "$": [ "ts", 192, - 1747403903262 + 1747664567742 ], - "$ts": 1747400303000 + "$ts": 1747660967000 } }, "type": "ia.input.date-time-input" @@ -31256,7 +31456,7 @@ }, "props": { "dismissOnSelect": false, - "formattedValue": "May 16, 2025 5:58 PM", + "formattedValue": "May 19, 2025 6:22 PM", "formattedValues": { "date": "Mar 29, 2021", "datetime": "Mar 29, 2021 1:37 PM", @@ -31266,9 +31466,9 @@ "$": [ "ts", 192, - 1747403903257 + 1747664567742 ], - "$ts": 1747403903000 + "$ts": 1747664567000 } }, "type": "ia.input.date-time-input" From 5092681d92bfc89e41cd46e82ccaee3e0fc1928a Mon Sep 17 00:00:00 2001 From: gugak <107577102+Salijoghli@users.noreply.github.com> Date: Mon, 19 May 2025 20:29:30 +0400 Subject: [PATCH 3/6] made error popup, showing it whenever connection is lost. added database connection status on the header --- .../views/PopUp-Views/ErrorPopup/view.json | 124 ++++++++++++++++++ .../views/Header/Header/view.json | 54 +++++++- 2 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 MTN6_SCADA/com.inductiveautomation.perspective/views/PopUp-Views/ErrorPopup/view.json diff --git a/MTN6_SCADA/com.inductiveautomation.perspective/views/PopUp-Views/ErrorPopup/view.json b/MTN6_SCADA/com.inductiveautomation.perspective/views/PopUp-Views/ErrorPopup/view.json new file mode 100644 index 0000000..5e9aa3e --- /dev/null +++ b/MTN6_SCADA/com.inductiveautomation.perspective/views/PopUp-Views/ErrorPopup/view.json @@ -0,0 +1,124 @@ +{ + "custom": {}, + "params": {}, + "props": { + "defaultSize": { + "height": 208, + "width": 450 + } + }, + "root": { + "children": [ + { + "meta": { + "name": "Label" + }, + "position": { + "height": 40, + "width": 360, + "x": 90 + }, + "props": { + "text": "Database Error", + "textStyle": { + "color": "#FF0000", + "fontFamily": "Arial", + "fontSize": "22px" + } + }, + "type": "ia.display.label" + }, + { + "meta": { + "name": "Icon" + }, + "position": { + "height": 40, + "width": 90 + }, + "props": { + "color": "#FF1D00", + "path": "material/error" + }, + "type": "ia.display.icon" + }, + { + "children": [ + { + "meta": { + "name": "Label" + }, + "position": { + "basis": "32px" + }, + "props": { + "text": "An error occurred while connecting to the database." + }, + "type": "ia.display.label" + }, + { + "meta": { + "name": "Label_0" + }, + "position": { + "basis": "32px" + }, + "props": { + "text": "Network or database connection failed. Check your settings or contact IT support." + }, + "type": "ia.display.label" + } + ], + "meta": { + "name": "FlexContainer" + }, + "position": { + "height": 99, + "width": 450, + "y": 48 + }, + "props": { + "alignItems": "flex-start", + "direction": "column", + "justify": "space-around", + "style": { + "paddingLeft": 5, + "paddingRight": 5 + } + }, + "type": "ia.container.flex" + }, + { + "events": { + "component": { + "onActionPerformed": { + "config": { + "script": "\tsystem.perspective.closePopup(\"\")" + }, + "scope": "G", + "type": "script" + } + } + }, + "meta": { + "name": "Button" + }, + "position": { + "height": 34, + "width": 206, + "x": 106, + "y": 158 + }, + "props": { + "primary": false, + "text": "Dismiss" + }, + "type": "ia.input.button" + } + ], + "meta": { + "name": "root" + }, + "type": "ia.container.coord" + } +} \ No newline at end of file diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json index 7981c25..8fc9818 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json @@ -6,9 +6,9 @@ "$": [ "ts", 192, - 1745914528657 + 1747671282220 ], - "$ts": 1745914528656 + "$ts": 1747671282219 } } }, @@ -442,6 +442,56 @@ }, "type": "ia.container.flex" }, + { + "meta": { + "name": "Icon_1", + "tooltip": { + "enabled": true, + "text": "Database Connection Status" + } + }, + "position": { + "basis": "30px" + }, + "propConfig": { + "props.color": { + "binding": { + "config": { + "expression": "{[System]Gateway/Database/MariaDB/Available}" + }, + "transforms": [ + { + "fallback": "#FF0000", + "inputType": "scalar", + "mappings": [ + { + "input": true, + "output": "#47FF47" + }, + { + "input": false, + "output": "#FF0000" + } + ], + "outputType": "color", + "type": "map" + } + ], + "type": "expr" + }, + "onChange": { + "enabled": null, + "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if not (system.tag.readBlocking([db_tag_path])[0].value):\n \tsystem.perspective.openPopup(\"errorPopup\",\u0027PopUp-Views/ErrorPopup\u0027)\n\t" + }, + "persistent": true + } + }, + "props": { + "color": "#47FF47", + "path": "material/table_chart" + }, + "type": "ia.display.icon" + }, { "meta": { "hasDelegate": true, From 74b6af0a7a2d0e2313d0d4aaa2b0e7b250b55de3 Mon Sep 17 00:00:00 2001 From: gugak <107577102+Salijoghli@users.noreply.github.com> Date: Mon, 19 May 2025 20:40:54 +0400 Subject: [PATCH 4/6] modified tooltip --- .../views/Header/Header/view.json | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json index 8fc9818..bfdbd20 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json @@ -6,9 +6,9 @@ "$": [ "ts", 192, - 1747671282220 + 1747672706122 ], - "$ts": 1747671282219 + "$ts": 1747672706122 } } }, @@ -446,14 +446,39 @@ "meta": { "name": "Icon_1", "tooltip": { - "enabled": true, - "text": "Database Connection Status" + "enabled": true } }, "position": { "basis": "30px" }, "propConfig": { + "meta.tooltip.text": { + "binding": { + "config": { + "expression": "{[System]Gateway/Database/MariaDB/Available}" + }, + "transforms": [ + { + "fallback": "Database Connection Status: LOST", + "inputType": "scalar", + "mappings": [ + { + "input": true, + "output": "Database Connection Status: GOOD" + }, + { + "input": false, + "output": "Database Connection Status: LOST" + } + ], + "outputType": "scalar", + "type": "map" + } + ], + "type": "expr" + } + }, "props.color": { "binding": { "config": { From c171fc7d3354cb5ed8028e99d757cbed3c07ca81 Mon Sep 17 00:00:00 2001 From: gugak <107577102+Salijoghli@users.noreply.github.com> Date: Mon, 19 May 2025 20:53:43 +0400 Subject: [PATCH 5/6] Put Database error inside parent project --- .../PopUp-Views/DatabaseError/resource.json | 17 +++++++++++++++++ .../PopUp-Views/DatabaseError/thumbnail.png | Bin 0 -> 16280 bytes .../views/PopUp-Views/DatabaseError}/view.json | 0 3 files changed, 17 insertions(+) create mode 100644 SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json create mode 100644 SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/thumbnail.png rename {MTN6_SCADA/com.inductiveautomation.perspective/views/PopUp-Views/ErrorPopup => SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError}/view.json (100%) diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json new file mode 100644 index 0000000..1cf75bf --- /dev/null +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json @@ -0,0 +1,17 @@ +{ + "scope": "G", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "view.json", + "thumbnail.png" + ], + "attributes": { + "lastModification": { + "actor": "admin", + "timestamp": "2025-05-19T16:51:36Z" + }, + "lastModificationSignature": "c96d5c63ec36ffa6e324173215ee03ed2ae751c82423c5716df1bba9e089940d" + } +} \ No newline at end of file diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/thumbnail.png b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..2403bbdeae2a3ba996ca261573b691bd8eec7557 GIT binary patch literal 16280 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 Date: Tue, 20 May 2025 13:52:55 +0400 Subject: [PATCH 6/6] changed style of the database error popup --- .../stylesheet/stylesheet.css | 18 +++++- .../views/Header/Header/view.json | 8 +-- .../PopUp-Views/DatabaseError/resource.json | 4 +- .../PopUp-Views/DatabaseError/thumbnail.png | Bin 16280 -> 9374 bytes .../views/PopUp-Views/DatabaseError/view.json | 57 ++++-------------- 5 files changed, 35 insertions(+), 52 deletions(-) diff --git a/MTN6_SCADA/com.inductiveautomation.perspective/stylesheet/stylesheet.css b/MTN6_SCADA/com.inductiveautomation.perspective/stylesheet/stylesheet.css index 72ca2a6..412f1a5 100644 --- a/MTN6_SCADA/com.inductiveautomation.perspective/stylesheet/stylesheet.css +++ b/MTN6_SCADA/com.inductiveautomation.perspective/stylesheet/stylesheet.css @@ -83,4 +83,20 @@ div[data-component="ia.input.fileupload"] .ia_button--primary { .psc-rotate { animation: rotation 2s infinite linear; -} \ No newline at end of file +} + +#popup-errorPopup .ia_popup__header { + color: red; + font-weight: bold; + font-size: 16px; +} + +#popup-errorPopup .ia_popup__header:before { + color: red; + content: "⛔ "; + font-weight: bold; + font-size: 16px; + padding-right: 4px; +} + + diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json index bfdbd20..c559ea8 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/Header/Header/view.json @@ -6,9 +6,9 @@ "$": [ "ts", 192, - 1747672706122 + 1747730947188 ], - "$ts": 1747672706122 + "$ts": 1747730947187 } } }, @@ -506,13 +506,13 @@ }, "onChange": { "enabled": null, - "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if not (system.tag.readBlocking([db_tag_path])[0].value):\n \tsystem.perspective.openPopup(\"errorPopup\",\u0027PopUp-Views/ErrorPopup\u0027)\n\t" + "script": " db_tag_path \u003d \"[System]Gateway/Database/MariaDB/Available\"\n\t\n if not (system.tag.readBlocking([db_tag_path])[0].value):\n \tsystem.perspective.openPopup(\"errorPopup\",\u0027PopUp-Views/DatabaseError\u0027, title\u003d\"Database Error\")\n\t" }, "persistent": true } }, "props": { - "color": "#47FF47", + "color": "#FF0000", "path": "material/table_chart" }, "type": "ia.display.icon" diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json index 1cf75bf..b44b07b 100644 --- a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json +++ b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/resource.json @@ -10,8 +10,8 @@ "attributes": { "lastModification": { "actor": "admin", - "timestamp": "2025-05-19T16:51:36Z" + "timestamp": "2025-05-20T09:49:07Z" }, - "lastModificationSignature": "c96d5c63ec36ffa6e324173215ee03ed2ae751c82423c5716df1bba9e089940d" + "lastModificationSignature": "cb7026f671f17bc4ef7ebb50ab9735c971cb50752a412e2a4e14d784891500f5" } } \ No newline at end of file diff --git a/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/thumbnail.png b/SCADA_PERSPECTIVE_PARENT_PROJECT/com.inductiveautomation.perspective/views/PopUp-Views/DatabaseError/thumbnail.png index 2403bbdeae2a3ba996ca261573b691bd8eec7557..5344c8700cdfc297ed1a5f374af5c962896e6f03 100644 GIT binary patch literal 9374 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