From af662f6fd274a6539de890be6de9ee7cb3c83521 Mon Sep 17 00:00:00 2001 From: "b.peck" Date: Thu, 16 Jul 2026 12:07:23 -0500 Subject: [PATCH] Phase 0: wipe PrimeBAT template, SimHarness (sim+probe), lint + schema harvest tools Gate G0 green: scan clean, lint 0 failures, clients 200, probe P0 facts captured, simulator journaling. Probe locked: uuid row grouping, compound AlarmState strings, java-exception bare-except gotcha, millis dates OK, ackUserName field. Co-Authored-By: Claude Fable 5 --- .../config/ignition/tags/valueStore.idb | Bin 20480 -> 20480 bytes .../page-config/config.json | 52 +- .../page-config/resource.json | 10 +- .../session-props/props.json | 24 +- .../session-props/resource.json | 10 +- .../Header/Alarm_Active/resource.json | 16 - .../Header/Alarm_Active/style.json | 16 - .../Header/DropDown/resource.json | 16 - .../style-classes/Header/DropDown/style.json | 14 - .../style-classes/Header/Header/resource.json | 16 - .../style-classes/Header/Header/style.json | 11 - .../style-classes/Header/Icon/resource.json | 16 - .../style-classes/Header/Icon/style.json | 9 - .../Menu/All_Header/resource.json | 16 - .../style-classes/Menu/All_Header/style.json | 12 - .../style-classes/Menu/Item/resource.json | 16 - .../style-classes/Menu/Item/style.json | 31 - .../style-classes/Menu/Menu/resource.json | 16 - .../style-classes/Menu/Menu/style.json | 7 - .../Page/Alarm/Alarm/resource.json | 16 - .../style-classes/Page/Alarm/Alarm/style.json | 14 - .../Page/Alarm/Page/resource.json | 16 - .../style-classes/Page/Alarm/Page/style.json | 7 - .../style-classes/Page/Bold/resource.json | 16 - .../style-classes/Page/Bold/style.json | 7 - .../style-classes/Page/Margins/resource.json | 16 - .../style-classes/Page/Margins/style.json | 10 - .../style-classes/Page/Page/resource.json | 16 - .../style-classes/Page/Page/style.json | 11 - .../style-classes/Page/Text/resource.json | 16 - .../style-classes/Page/Text/style.json | 8 - .../style-classes/Title/Icon/resource.json | 16 - .../style-classes/Title/Icon/style.json | 7 - .../style-classes/Title/Text/resource.json | 16 - .../style-classes/Title/Text/style.json | 12 - .../style-classes/Title/Title/resource.json | 16 - .../style-classes/Title/Title/style.json | 13 - .../views/Docks/Menu/resource.json | 17 - .../views/Docks/Menu/thumbnail.png | Bin 4262 -> 0 bytes .../views/Docks/Menu/view.json | 135 ---- .../Breakpoint Embedded/resource.json | 17 - .../Breakpoint Embedded/thumbnail.png | Bin 270 -> 0 bytes .../Framework/Breakpoint Embedded/view.json | 107 --- .../views/Framework/Breakpoint/resource.json | 17 - .../views/Framework/Breakpoint/thumbnail.png | Bin 357 -> 0 bytes .../views/Framework/Breakpoint/view.json | 118 ---- .../views/Header/Header/resource.json | 17 - .../views/Header/Header/thumbnail.png | Bin 2274 -> 0 bytes .../views/Header/Header/view.json | 643 ------------------ .../views/Page/Alarms/resource.json | 17 - .../views/Page/Alarms/thumbnail.png | Bin 17151 -> 0 bytes .../views/Page/Alarms/view.json | 66 -- .../views/Page/Charts/resource.json | 17 - .../views/Page/Charts/thumbnail.png | Bin 13476 -> 0 bytes .../views/Page/Charts/view.json | 60 -- .../views/Page/Embedded/Title/resource.json | 17 - .../views/Page/Embedded/Title/thumbnail.png | Bin 836 -> 0 bytes .../views/Page/Embedded/Title/view.json | 89 --- .../views/Page/Home/resource.json | 17 - .../views/Page/Home/thumbnail.png | Bin 23607 -> 0 bytes .../views/Page/Home/view.json | 92 --- .../PrimeControls/Dashboard/resource.json | 10 + .../views/PrimeControls/Dashboard/view.json | 40 ++ .../page-config/config.json | 9 + .../page-config/resource.json | 10 + .../views/Scratch/Current/resource.json | 10 + .../views/Scratch/Current/view.json | 27 + .../ignition/script-python/alarmsim/code.py | 179 +++++ .../script-python/alarmsim/resource.json | 12 + .../ignition/script-python/probe/code.py | 179 +++++ .../script-python/probe/resource.json | 12 + .../timer/AlarmSimTick/handleTimerEvent.py | 2 + .../ignition/timer/AlarmSimTick/resource.json | 15 + .../timer/ProbeTick/handleTimerEvent.py | 2 + .../ignition/timer/ProbeTick/resource.json | 15 + .../gateway/projects/SimHarness/project.json | 7 + tools/lint_project.py | 297 ++++++++ tools/schema_harvest.sh | 55 ++ 78 files changed, 911 insertions(+), 1957 deletions(-) delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/Alarm_Active/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/Alarm_Active/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/DropDown/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/DropDown/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/Header/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/Header/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/Icon/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Header/Icon/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Menu/All_Header/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Menu/All_Header/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Menu/Item/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Menu/Item/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Menu/Menu/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Menu/Menu/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Alarm/Alarm/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Alarm/Alarm/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Alarm/Page/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Alarm/Page/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Bold/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Bold/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Margins/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Margins/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Page/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Page/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Text/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Page/Text/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Title/Icon/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Title/Icon/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Title/Text/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Title/Text/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Title/Title/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/style-classes/Title/Title/style.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Docks/Menu/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Docks/Menu/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Docks/Menu/view.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Framework/Breakpoint Embedded/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Framework/Breakpoint Embedded/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Framework/Breakpoint Embedded/view.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Framework/Breakpoint/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Framework/Breakpoint/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Framework/Breakpoint/view.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Header/Header/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Header/Header/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Header/Header/view.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/view.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Charts/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Charts/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Charts/view.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/view.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Home/resource.json delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Home/thumbnail.png delete mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Home/view.json create mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/resource.json create mode 100644 ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/view.json create mode 100644 ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/config.json create mode 100644 ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/resource.json create mode 100644 ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/resource.json create mode 100644 ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/view.json create mode 100644 ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/code.py create mode 100644 ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/resource.json create mode 100644 ignition/gateway/projects/SimHarness/ignition/script-python/probe/code.py create mode 100644 ignition/gateway/projects/SimHarness/ignition/script-python/probe/resource.json create mode 100644 ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/handleTimerEvent.py create mode 100644 ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/resource.json create mode 100644 ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/handleTimerEvent.py create mode 100644 ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/resource.json create mode 100644 ignition/gateway/projects/SimHarness/project.json create mode 100644 tools/lint_project.py create mode 100755 tools/schema_harvest.sh diff --git a/ignition/gateway/config/ignition/tags/valueStore.idb b/ignition/gateway/config/ignition/tags/valueStore.idb index ae8f8d5877e5a7b2e03601009c5fb61641de2db1..19bdb53697cdf335750b4b12b8dfeada68fc5123 100644 GIT binary patch delta 751 zcmYjPOK4M35N%YF-1lOWhe=BM(j=NqRVtE%q|N6gi1|Vz^@Ft{7(^qq#MVNCZmc^$ zK_e5yRr-huUA72HyK<#qwdtyh5Zb)o&SG4MtD^VjQ?j`*bLPyPGjkVt)grH2K`y$n zaYVH=-PpLBI15dW9r!c_AyIIw^>c5j`V8Ukyhg$=6*C5(mdyMnDYYO~xy-*=I z9&*}puG#yy=jUhc<<)(kF)B|t$vbm7dWvGNLdIoLM9A^$9Nc&e4*@kVslc4juR!N*M>*%7rYc6|x^= zR*JpWoog61B5`RCBO*TmBctzIGo7%X(&^hgv2cr^g|EPj0i%>haHN!9#mom?x(d<2 z!I~6pkd$fpxCxuYYZ`_V{i39mE3+5+WSNgh&N4f!7_(;nspB zXr^Uh7-O zF>S82ynqX&jK5gzOl-Et9*ZPB~x~q_Ux|`fw&l3J}wx09a!eLsTyhCV{-%^KQ W%a1=WhoBhFB9RN>yvS855yO8xULZLD delta 717 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 diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Header/Header/view.json b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Header/Header/view.json deleted file mode 100644 index 6542e6f..0000000 --- a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Header/Header/view.json +++ /dev/null @@ -1,643 +0,0 @@ -{ - "custom": {}, - "params": { - "params": {}, - "size": "medium" - }, - "propConfig": { - "params.params": { - "paramDirection": "input", - "persistent": true - }, - "params.size": { - "paramDirection": "input" - } - }, - "props": { - "defaultSize": { - "height": 58 - } - }, - "root": { - "children": [ - { - "meta": { - "hasDelegate": true, - "name": "Spacer Start" - }, - "position": { - "basis": "16px", - "shrink": 0 - }, - "props": { - "text": " " - }, - "type": "ia.display.label" - }, - { - "events": { - "dom": { - "onClick": { - "config": { - "id": "menu", - "type": "toggle" - }, - "scope": "C", - "type": "dock" - } - } - }, - "meta": { - "name": "Menu Dock" - }, - "position": { - "basis": "24px", - "shrink": 0 - }, - "propConfig": { - "position.display": { - "binding": { - "config": { - "expression": "{view.params.size} !\u003d \"large\"" - }, - "type": "expr" - } - } - }, - "props": { - "path": "material/menu", - "style": { - "classes": "Header/Icon", - "marginRight": "10px" - } - }, - "type": "ia.display.icon" - }, - { - "children": [ - { - "children": [ - { - "children": [ - { - "meta": { - "name": "path2" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 37.344391,71.227685 v 25.297 h -3.817 v -25.297 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path4" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 56.308391,96.148685 c 0,0.754 -0.029,1.417 -0.075,1.991 -0.049,0.574 -0.118,1.077 -0.205,1.513 -0.262,1.201995 -0.775,2.235995 -1.542,3.097995 -1.438,1.664 -3.425,2.491 -5.945,2.491 -2.13,0 -3.884,-0.575 -5.26,-1.728 -1.421,-1.186 -2.24,-2.821 -2.458,-4.907995 h 3.702 c 0.144,0.787 0.378,1.393998 0.706,1.818995 0.764,0.993 1.879,1.49 3.342,1.49 2.698,0 4.047,-1.646 4.047,-4.945995 v -2.228 c -1.464,1.496 -3.152,2.244 -5.063,2.244 -2.172,0 -3.953,-0.788 -5.341,-2.359 -1.396,-1.595 -2.096,-3.589 -2.096,-5.98 0,-2.327 0.649,-4.303 1.949,-5.93 1.398,-1.729 3.244,-2.59 5.538,-2.59 2.011,0 3.681,0.746 5.014,2.245 v -1.803 h 3.688 v 15.581 z m -3.54,-7.561 c 0,-1.549 -0.417,-2.789 -1.245,-3.713 -0.839,-0.949 -1.915,-1.424 -3.227,-1.424 -1.4,0 -2.503,0.518 -3.312,1.553 -0.73,0.927 -1.098,2.129 -1.098,3.599 0,1.451 0.367,2.639 1.098,3.566 0.797,1.016 1.902,1.523 3.312,1.523 1.408,0 2.522,-0.513 3.342,-1.539 0.751,-0.927 1.13,-2.114 1.13,-3.565 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path6" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 59.950391,80.567685 h 3.705 v 1.474 c 1.29,-1.278 2.743,-1.916 4.36,-1.916 1.857,0 3.305,0.585 4.342,1.753 0.895,0.994 1.344,2.615 1.344,4.865 v 9.782 h -3.703 v -8.902 c 0,-1.569 -0.221,-2.656 -0.657,-3.257 -0.425,-0.609 -1.202,-0.916 -2.326,-0.916 -1.224,0 -2.093,0.403 -2.606,1.21 -0.502,0.797 -0.754,2.189 -0.754,4.173 v 7.692 h -3.705 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path8" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 76.790391,73.930685 c 0,-0.644 0.233,-1.202 0.702,-1.67 0.473,-0.47 1.033,-0.703 1.691,-0.703 0.663,0 1.234,0.233 1.701,0.703 0.473,0.457 0.708,1.023 0.708,1.688 0,0.667 -0.235,1.234 -0.708,1.704 -0.456,0.468 -1.02,0.705 -1.685,0.705 -0.669,0 -1.234,-0.236 -1.707,-0.705 -0.469,-0.471 -0.702,-1.045 -0.702,-1.722 z m 4.241,6.637 v 15.958 h -3.687 v -15.958 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path10" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 89.170391,84.006685 v 12.519 h -3.689 v -12.519 h -1.574 v -3.439 h 1.574 v -5.849 h 3.689 v 5.849 h 2.865 v 3.439 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path12" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 93.649391,73.930685 c 0,-0.644 0.233,-1.202 0.705,-1.67 0.468,-0.47 1.031,-0.703 1.688,-0.703 0.667,0 1.233,0.233 1.704,0.703 0.468,0.457 0.705,1.023 0.705,1.688 0,0.667 -0.236,1.234 -0.705,1.704 -0.459,0.468 -1.021,0.705 -1.688,0.705 -0.666,0 -1.235,-0.236 -1.704,-0.705 -0.472,-0.471 -0.705,-1.045 -0.705,-1.722 z m 4.243,6.637 v 15.958 h -3.687 v -15.958 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path14" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 100.80139,88.432685 c 0,-2.305 0.826,-4.266 2.476,-5.881 1.651,-1.619 3.66,-2.426 6.03,-2.426 2.381,0 4.4,0.813 6.062,2.442 1.636,1.627 2.459,3.628 2.459,5.998 0,2.389 -0.83,4.395 -2.476,6.011 -1.662,1.604 -3.698,2.408 -6.111,2.408 -2.393,0 -4.398,-0.816 -6.014,-2.458 -1.619,-1.616 -2.426,-3.646 -2.426,-6.094 z m 3.77,0.067 c 0,1.59 0.425,2.856 1.278,3.783 0.873,0.938 2.025,1.41 3.457,1.41 1.44,0 2.596,-0.463 3.457,-1.394 0.861,-0.927 1.294,-2.167 1.294,-3.721 0,-1.55 -0.433,-2.791 -1.294,-3.717 -0.873,-0.94 -2.028,-1.41 -3.457,-1.41 -1.411,0 -2.552,0.47 -3.425,1.41 -0.874,0.938 -1.31,2.151 -1.31,3.639 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path16" - }, - "props": { - "fill": { - "opacity": "1" - }, - "path": "m 132.36239,82.585685 c -0.544,-0.74 -1.026,-1.406 -1.483,-2.036 -0.715,-0.279 -1.523,-0.425 -2.434,-0.425 -1.619,0 -3.07,0.638 -4.36,1.916 v -1.474 h -3.703 v 15.958 h 3.703 v -7.692 c 0,-1.983 0.253,-3.375 0.755,-4.173 0.513,-0.807 1.384,-1.21 2.606,-1.21 1.125,0 1.898,0.307 2.326,0.916 0.434,0.601 0.652,1.688 0.652,3.257 v 8.902 h 3.705 v -9.782 c 0,-0.719 -0.05,-1.37 -0.143,-1.963 -0.587,-0.792 -1.142,-1.54 -1.624,-2.194 z", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path18" - }, - "props": { - "fill": { - "paint": "#f7901e" - }, - "path": "m 170.41439,49.972685 c -0.428,-0.949 -1.103,-1.685 -2.024,-2.208 -0.927,-0.521 -1.903,-0.723 -2.933,-0.598 -1.034,0.12 -1.923,0.836 -2.664,2.151 -0.14,0.241 -0.494,0.984 -1.069,2.232 -0.578,1.245 -1.313,2.821 -2.2,4.728 -0.885,1.903 -1.892,4.048 -3.013,6.43 -1.126,2.381 -2.287,4.887 -3.484,7.513 -1.202,2.627 -2.412,5.277 -3.638,7.956 -1.201,2.625 -2.346,5.116 -3.426,7.47 0.518,0.222 1.007,0.508 1.473,0.854 2.222,-3.257 4.599,-6.724 7.125,-10.401 2.557,-3.722 4.931,-7.207 7.126,-10.46 2.196,-3.25 4.064,-6.014 5.606,-8.284 1.544,-2.271 2.425,-3.602 2.644,-3.991 0.745,-1.316 0.905,-2.443 0.477,-3.392 z" - }, - "type": "ia.shapes.path" - }, - { - "meta": { - "name": "path20" - }, - "props": { - "fill": { - "paint": "#f7901e" - }, - "path": "m 139.79939,85.710685 c -0.701,-2.8 -1.275,-5.053 -1.715,-6.762 -0.449,-1.726 -0.823,-3.093 -1.114,-4.1 -0.29,-1.003 -0.521,-1.742 -0.69,-2.21 -0.171,-0.47 -0.335,-0.871 -0.493,-1.2 -0.377,-0.79 -0.934,-1.306 -1.666,-1.547 -0.734,-0.241 -1.518,-0.161 -2.351,0.237 -0.834,0.398 -1.389,0.96 -1.66,1.681 -0.276,0.724 -0.223,1.48 0.15,2.27 0.159,0.329 0.367,0.708 0.626,1.136 0.258,0.427 0.686,1.072 1.287,1.931 0.598,0.858 1.429,2.002 2.488,3.435 1.058,1.432 2.469,3.31 4.23,5.643 0.296,-0.193 0.599,-0.369 0.908,-0.514 z" - }, - "type": "ia.shapes.path" - }, - { - "children": [ - { - "meta": { - "name": "path64" - }, - "props": { - "fill": { - "paint": "#f7901e" - }, - "path": "m 336.294,345.251 c 0,-0.837 0.668,-1.505 1.513,-1.505 0.846,0 1.512,0.668 1.512,1.505 0,0.838 -0.666,1.504 -1.512,1.504 -0.845,0 -1.513,-0.666 -1.513,-1.504 z m 2.794,0 c 0,-0.709 -0.591,-1.274 -1.281,-1.274 -0.692,0 -1.28,0.565 -1.28,1.274 0,0.709 0.588,1.273 1.28,1.273 0.69,0 1.281,-0.564 1.281,-1.273 z m -0.564,0.814 h -0.279 l -0.494,-0.76 h -0.274 v 0.76 h -0.234 v -1.647 h 0.72 c 0.297,0 0.585,0.081 0.585,0.435 0,0.324 -0.226,0.444 -0.52,0.453 z m -0.773,-0.992 c 0.206,0 0.565,0.04 0.565,-0.223 0,-0.17 -0.226,-0.2 -0.402,-0.2 h -0.438 v 0.423 z" - }, - "type": "ia.shapes.path" - } - ], - "meta": { - "name": "g66" - }, - "props": { - "transform": "translate(-185.14061,-250.00331)" - }, - "type": "ia.shapes.group" - }, - { - "meta": { - "name": "circle68" - }, - "props": { - "cx": "142.94638", - "cy": "92.620682", - "fill": { - "paint": "#f7901e" - }, - "r": "4.652" - }, - "type": "ia.shapes.circle" - } - ], - "meta": { - "name": "g196" - }, - "props": { - "transform": "translate(0,1.2700031)" - }, - "type": "ia.shapes.group" - } - ], - "meta": { - "name": "g111" - }, - "props": { - "transform": "translate(-33.527391,-48.400683)" - }, - "type": "ia.shapes.group" - } - ], - "events": { - "dom": { - "onClick": { - "config": { - "page": "/" - }, - "scope": "C", - "type": "nav" - } - } - }, - "meta": { - "name": "Ignition Logo" - }, - "position": { - "basis": "100px", - "shrink": 0 - }, - "props": { - "style": { - "cursor": "pointer" - }, - "viewBox": "0 0 137.121 58.111001" - }, - "type": "ia.container.drawing" - }, - { - "meta": { - "hasDelegate": true, - "name": "Filler" - }, - "position": { - "grow": 1 - }, - "props": { - "text": " " - }, - "type": "ia.display.label" - }, - { - "meta": { - "name": "Dropdown" - }, - "position": { - "basis": "124px", - "shrink": 0 - }, - "propConfig": { - "props.value": { - "binding": { - "config": { - "bidirectional": true, - "path": "session.props.theme" - }, - "type": "property" - } - } - }, - "props": { - "options": [ - { - "label": "light", - "value": "light" - }, - { - "label": "light-cool", - "value": "light-cool" - }, - { - "label": "light-warm", - "value": "light-warm" - }, - { - "label": "dark", - "value": "dark" - }, - { - "label": "dark-cool", - "value": "dark-cool" - }, - { - "label": "dark-warm", - "value": "dark-warm" - } - ], - "search": { - "enabled": false - }, - "style": { - "classes": "Header/DropDown", - "textAlign": "left" - } - }, - "type": "ia.input.dropdown" - }, - { - "meta": { - "hasDelegate": true, - "name": "Spacer 1" - }, - "position": { - "basis": "10px" - }, - "props": { - "text": " " - }, - "type": "ia.display.label" - }, - { - "events": { - "dom": { - "onClick": { - "config": { - "script": "\tif self.session.props.auth.authenticated:\n\t\tsystem.perspective.logout()\n\telse:\n\t\tsystem.perspective.login()" - }, - "scope": "G", - "type": "script" - } - } - }, - "meta": { - "name": "User" - }, - "position": { - "basis": "30px", - "shrink": 0 - }, - "props": { - "path": "material/person", - "style": { - "classes": "Header/Icon" - } - }, - "type": "ia.display.icon" - }, - { - "events": { - "dom": { - "onClick": { - "config": { - "script": "\tif self.session.props.auth.authenticated:\n\t\tsystem.perspective.logout()\n\telse:\n\t\tsystem.perspective.login()" - }, - "scope": "G", - "type": "script" - } - } - }, - "meta": { - "hasDelegate": true, - "name": "Sign In" - }, - "position": { - "shrink": 0 - }, - "propConfig": { - "position.display": { - "binding": { - "config": { - "expression": "{view.params.size} !\u003d \"small\"" - }, - "type": "expr" - } - }, - "props.text": { - "binding": { - "config": { - "path": "session.props.auth.user.userName" - }, - "transforms": [ - { - "expression": "if({value} \u003d null || len({value}) \u003d 0, \"Sign In\", {value})", - "type": "expression" - } - ], - "type": "property" - } - } - }, - "props": { - "style": { - "classes": "Header/Icon", - "cursor": "pointer", - "marginLeft": "4px" - } - }, - "type": "ia.display.label" - }, - { - "meta": { - "hasDelegate": true, - "name": "Spacer 2" - }, - "position": { - "basis": "10px", - "shrink": 0 - }, - "props": { - "text": " " - }, - "type": "ia.display.label" - }, - { - "events": { - "dom": { - "onClick": { - "config": { - "page": "/alarms" - }, - "scope": "C", - "type": "nav" - } - } - }, - "meta": { - "name": "Alarm Icon" - }, - "position": { - "basis": "26px", - "shrink": 0 - }, - "propConfig": { - "props.style.classes": { - "binding": { - "config": { - "path": "../Alarms.custom.numAlarms" - }, - "transforms": [ - { - "expression": "if({value} \u003e 0, \"Header/Alarm_Active\", \"Header/Icon\")", - "type": "expression" - } - ], - "type": "property" - } - } - }, - "props": { - "path": "material/alarm" - }, - "type": "ia.display.icon" - }, - { - "events": { - "dom": { - "onClick": { - "config": { - "page": "/alarms" - }, - "scope": "C", - "type": "nav" - } - } - }, - "meta": { - "hasDelegate": true, - "name": "Alarms" - }, - "position": { - "shrink": 0 - }, - "propConfig": { - "custom.numAlarms": { - "binding": { - "config": { - "expression": "now(2000)" - }, - "transforms": [ - { - "code": "\treturn len(system.alarm.queryStatus(state\u003d[\"ActiveUnacked\"]))", - "type": "script" - } - ], - "type": "expr" - } - }, - "position.display": { - "binding": { - "config": { - "expression": "{view.params.size} !\u003d \"small\"" - }, - "type": "expr" - } - }, - "props.style.classes": { - "binding": { - "config": { - "path": "../Alarms.custom.numAlarms" - }, - "transforms": [ - { - "expression": "if({value} \u003e 0, \"Header/Alarm_Active\", \"\")", - "type": "expression" - } - ], - "type": "property" - } - }, - "props.text": { - "binding": { - "config": { - "path": "this.custom.numAlarms" - }, - "transforms": [ - { - "expression": "numberFormat({value}, \"#,##0\") + \" alarm\" + if({value} \u003e 1 || {value} \u003d 0, \"s\", \"\")", - "type": "expression" - } - ], - "type": "property" - } - } - }, - "props": { - "style": { - "cursor": "pointer", - "marginLeft": "6px" - } - }, - "type": "ia.display.label" - }, - { - "meta": { - "hasDelegate": true, - "name": "Spacer End" - }, - "position": { - "basis": "16px", - "shrink": 0 - }, - "props": { - "text": " " - }, - "type": "ia.display.label" - } - ], - "meta": { - "name": "root" - }, - "propConfig": { - "props.style.backgroundColor": { - "binding": { - "config": { - "path": "session.props.theme" - }, - "transforms": [ - { - "code": "\tif \"dark\" in value:\n\t\treturn \"var(--neutral-10)\"\n\telse:\n\t\treturn \"\"", - "type": "script" - } - ], - "type": "property" - } - } - }, - "props": { - "style": { - "classes": "Header/Header" - } - }, - "type": "ia.container.flex" - } -} \ No newline at end of file diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/resource.json b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/resource.json deleted file mode 100644 index a85e7ee..0000000 --- a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/resource.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "scope": "G", - "version": 1, - "restricted": false, - "overridable": true, - "files": [ - "view.json", - "thumbnail.png" - ], - "attributes": { - "lastModificationSignature": "295c4883382d0243d3e8270d5a208fec97cf3bd84a43573e894c552707c0480c", - "lastModification": { - "actor": "admin", - "timestamp": "2026-07-16T16:25:30Z" - } - } -} \ No newline at end of file diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/thumbnail.png b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Alarms/thumbnail.png deleted file mode 100644 index e39679f51d231bdece1d29c11757308648b3a4d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17151 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 b&X*eN#~#JiuWR7jM{ts2a-wD6kAD9LP8RCF diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Charts/view.json b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Charts/view.json deleted file mode 100644 index 622f8d8..0000000 --- a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Charts/view.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "custom": {}, - "params": {}, - "propConfig": { - "params.feature": { - "paramDirection": "input" - }, - "params.key": { - "paramDirection": "input" - }, - "params.tier1": { - "paramDirection": "input" - }, - "params.tier2": { - "paramDirection": "input" - } - }, - "props": {}, - "root": { - "children": [ - { - "meta": { - "name": "Title" - }, - "position": { - "basis": "56px", - "shrink": 0 - }, - "props": { - "params": { - "icon": "show_chart", - "text": "Charts" - }, - "path": "Page/Embedded/Title" - }, - "type": "ia.display.view" - }, - { - "meta": { - "name": "PowerChart" - }, - "position": { - "basis": "600px", - "grow": 1 - }, - "type": "ia.chart.powerchart" - } - ], - "meta": { - "name": "root" - }, - "props": { - "direction": "column", - "style": { - "classes": "Page/Page" - } - }, - "type": "ia.container.flex" - } -} \ No newline at end of file diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/resource.json b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/resource.json deleted file mode 100644 index 2e9fc54..0000000 --- a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/resource.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "scope": "G", - "version": 1, - "restricted": false, - "overridable": true, - "files": [ - "view.json", - "thumbnail.png" - ], - "attributes": { - "lastModificationSignature": "4faccd858e984072a9442439dc3f60d3c0332ce5d8c8f116e14cb6037916b80e", - "lastModification": { - "actor": "admin", - "timestamp": "2026-07-16T16:25:30Z" - } - } -} \ No newline at end of file diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/thumbnail.png b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Embedded/Title/thumbnail.png deleted file mode 100644 index e6045aaf940f47241de64eaaf1c93161a638b200..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 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 g{#-2&!*e-OpYAuBTS1@*@0y0FFcG$^ZZW diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Home/view.json b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Home/view.json deleted file mode 100644 index 20c2372..0000000 --- a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/Page/Home/view.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "custom": {}, - "params": {}, - "propConfig": { - "params.feature": { - "paramDirection": "input" - }, - "params.key": { - "paramDirection": "input" - }, - "params.tier1": { - "paramDirection": "input" - }, - "params.tier2": { - "paramDirection": "input" - } - }, - "props": {}, - "root": { - "children": [ - { - "meta": { - "name": "Title" - }, - "position": { - "basis": "56px", - "shrink": 0 - }, - "props": { - "params": { - "icon": "home", - "text": "Home" - }, - "path": "Page/Embedded/Title" - }, - "type": "ia.display.view" - }, - { - "meta": { - "hasDelegate": true, - "name": "Welcome" - }, - "position": { - "basis": "20px", - "shrink": 0 - }, - "props": { - "style": { - "classes": "Page/Bold Page/Margins Page/Text" - }, - "text": "Welcome to Perspective!" - }, - "type": "ia.display.label" - }, - { - "meta": { - "hasDelegate": true, - "name": "Description" - }, - "position": { - "shrink": 0 - }, - "props": { - "style": { - "classes": "Page/Text Page/Margins" - }, - "text": "The skeleton project is intended to help you get started with Perspective. The project is designed to be mobile-responsive and fully utilizes styles. Feel free to modify as desired." - }, - "type": "ia.display.label" - }, - { - "meta": { - "name": "CoordinateContainer" - }, - "position": { - "grow": 1 - }, - "type": "ia.container.coord" - } - ], - "meta": { - "name": "root" - }, - "props": { - "direction": "column", - "style": { - "classes": "Page/Page" - } - }, - "type": "ia.container.flex" - } -} \ No newline at end of file diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/resource.json b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/resource.json new file mode 100644 index 0000000..276dced --- /dev/null +++ b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/resource.json @@ -0,0 +1,10 @@ +{ + "scope": "G", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "view.json" + ], + "attributes": {} +} diff --git a/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/view.json b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/view.json new file mode 100644 index 0000000..c7c454b --- /dev/null +++ b/ignition/gateway/projects/PrimeBAT/com.inductiveautomation.perspective/views/PrimeControls/Dashboard/view.json @@ -0,0 +1,40 @@ +{ + "custom": {}, + "params": {}, + "propConfig": {}, + "props": { + "defaultSize": { + "height": 900, + "width": 1600 + } + }, + "root": { + "children": [ + { + "meta": { + "name": "lblPlaceholder" + }, + "position": { + "grow": 0 + }, + "props": { + "text": "PrimeControls Dashboard - under construction (Phase 0 placeholder)", + "textStyle": { + "fontSize": "1.2em" + } + }, + "type": "ia.display.label" + } + ], + "custom": {}, + "meta": { + "name": "root" + }, + "props": { + "alignItems": "center", + "direction": "column", + "justify": "center" + }, + "type": "ia.container.flex" + } +} diff --git a/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/config.json b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/config.json new file mode 100644 index 0000000..0058275 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/config.json @@ -0,0 +1,9 @@ +{ + "pages": { + "/scratch": { + "title": "Scratch", + "viewPath": "Scratch/Current" + } + }, + "sharedDocks": {} +} diff --git a/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/resource.json b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/resource.json new file mode 100644 index 0000000..b219fa3 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/page-config/resource.json @@ -0,0 +1,10 @@ +{ + "scope": "G", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "config.json" + ], + "attributes": {} +} diff --git a/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/resource.json b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/resource.json new file mode 100644 index 0000000..276dced --- /dev/null +++ b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/resource.json @@ -0,0 +1,10 @@ +{ + "scope": "G", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "view.json" + ], + "attributes": {} +} diff --git a/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/view.json b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/view.json new file mode 100644 index 0000000..85185c1 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/com.inductiveautomation.perspective/views/Scratch/Current/view.json @@ -0,0 +1,27 @@ +{ + "custom": {}, + "params": {}, + "propConfig": {}, + "props": {}, + "root": { + "children": [ + { + "meta": { + "name": "lblScratch" + }, + "props": { + "text": "SimHarness scratch view - overwrite me to isolate a component under test." + }, + "type": "ia.display.label" + } + ], + "custom": {}, + "meta": { + "name": "root" + }, + "props": { + "direction": "column" + }, + "type": "ia.container.flex" + } +} diff --git a/ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/code.py b/ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/code.py new file mode 100644 index 0000000..3efb76c --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/code.py @@ -0,0 +1,179 @@ +# alarm_simulator.py — Build-a-Thon alarm activity generator (Jython, Ignition 8.3) +# +# Drives the Boolean memory tags in [default]BuildathonSim/ (import +# simulation_tags.json first). Designed to be called every 5 seconds: +# +# RECOMMENDED — Gateway timer script (survives Designer close): +# 1. Designer > Project Library: create a script named "alarmsim", paste this file. +# 2. Project > Gateway Events > Timer: new script, Delay 5000 ms, +# Fixed Delay, Dedicated thread. Body: alarmsim.tick() +# 3. Save the project. Alarm activity starts immediately and runs forever. +# +# ALTERNATIVE — Designer Script Console (blocks the console while running): +# Paste this whole file, then add at the bottom: +# run_console(minutes=30) +# +# Patterns produced (all timestamps land in the alarm journal): +# - Baseline: random alarms across 5 areas / 3 priorities, active 30 s - 5 min, +# averaging a few activations per minute +# - Chattering: *_Chatter tags re-trigger every 30-60 s (short 5-15 s actives) +# - Standing: *_Standing tags go active on the first tick and never clear +# - Fleeting: *_Fleeting tags are active < 10 s +# - Flood: roughly every 30-60 min (or on demand via force_flood()), one area +# bursts 20-40 activations over 10 minutes +# +# Manual helpers (Script Console, gateway scope via system.util.sendRequest not +# needed — just call from a console if running console mode, or temporarily from +# the timer script): +# alarmsim.force_flood() -> start a flood burst now +# alarmsim.reset() -> clear all sim tags and internal state + +import random +import time + +BASE = "[default]BuildathonSim" + +BASELINE = { + "Intake": ["Pump1_Fault", "Pump2_Fault", "ScreenDiffPressHigh", "InletFlowLow", + "ValveV101_FailToOpen", "WetWellLevelHigh", "SampleTempHigh", "PowerMonitorAlarm"], + "BoilerHouse": ["FeedPumpA_Fault", "FeedPumpB_Fault", "SteamPressHigh", "DrumLevelLow", + "StackTempHigh", "FuelGasPressLow", "EconomizerDPHigh", "BlowdownConductivityHigh"], + "Packaging": ["LabelerFault", "CapperTorqueLow", "FillerLevelDeviation", "ConveyorOverload", + "CaseSealerJam", "PrinterInkLow", "RejectRateHigh", "GuardDoorOpen"], + "Utilities": ["AirCompA_Fault", "AirHeaderPressLow", "ChillerTripped", "CoolingTowerVibration", + "GlycolTempHigh", "N2PressLow", "UPSOnBattery", "WaterSoftenerFault"], + "TankFarm": ["T101_LevelHigh", "T102_LevelHigh", "T103_TempHigh", "TransferPumpFault", + "ContainmentSumpHigh", "VaporRecoveryFault", "T104_PressHigh", "ManifoldLeakDetect"], +} + +CHATTER = [ + "Intake/ConveyorJam_Chatter", + "Packaging/ShrinkTunnelTemp_Chatter", + "Utilities/AirDryerDewpoint_Chatter", +] +STANDING = [ + "BoilerHouse/DrumLevelXmtrFail_Standing", + "TankFarm/T105_LevelXmtrFail_Standing", +] +FLEETING = [ + "Intake/CommsGlitch_Fleeting", + "BoilerHouse/FlameScannerFlicker_Fleeting", + "Packaging/EStopPulse_Fleeting", + "TankFarm/PumpSealFlushLow_Fleeting", +] + +# Tuning (per 5 s tick) +BASELINE_PROB = 0.30 # ~3-4 baseline activations/min across the plant +FLEETING_PROB = 0.08 # ~1 fleeting alarm/min +FLOOD_START_PROB = 0.0025 # expected flood every ~30-60 min +FLOOD_DURATION = 600 # 10 minutes +FLOOD_EVENT_PROB = 0.35 # per tick during flood -> ~25 activations / 10 min + + +def _path(rel): + return "%s/%s" % (BASE, rel) + + +def _state(): + g = system.util.getGlobals() + if "buildathon_alarmsim" not in g: + g["buildathon_alarmsim"] = { + "clears": {}, # tag path -> epoch seconds to write False + "chatter_next": {}, # chatter path -> epoch seconds of next re-trigger + "flood": None, # {"area": name, "until": epoch} while flooding + "standing_set": False, + } + return g["buildathon_alarmsim"] + + +def _write(pairs): + if pairs: + system.tag.writeBlocking([p for p, v in pairs], [v for p, v in pairs]) + + +def tick(): + st = _state() + now = time.time() + writes = [] + + # 1. Standing alarms: activate once, never clear + if not st["standing_set"]: + writes += [(_path(p), True) for p in STANDING] + st["standing_set"] = True + + # 2. Process scheduled clears + for path in list(st["clears"].keys()): + if now >= st["clears"][path]: + writes.append((path, False)) + del st["clears"][path] + + # 3. Baseline activity: random tag, active 30 s - 5 min + if random.random() < BASELINE_PROB: + area = random.choice(list(BASELINE.keys())) + path = _path("%s/%s" % (area, random.choice(BASELINE[area]))) + if path not in st["clears"]: + writes.append((path, True)) + st["clears"][path] = now + random.uniform(30, 300) + + # 4. Chattering: re-trigger every 30-60 s, active 5-15 s each time + for rel in CHATTER: + path = _path(rel) + nxt = st["chatter_next"].get(path, 0) + if now >= nxt and path not in st["clears"]: + writes.append((path, True)) + st["clears"][path] = now + random.uniform(5, 15) + st["chatter_next"][path] = now + random.uniform(30, 60) + + # 5. Fleeting: active 2-8 s + if random.random() < FLEETING_PROB: + path = _path(random.choice(FLEETING)) + if path not in st["clears"]: + writes.append((path, True)) + st["clears"][path] = now + random.uniform(2, 8) + + # 6. Flood burst: one area, 15+ activations inside 10 minutes + if st["flood"] is None: + if random.random() < FLOOD_START_PROB: + _start_flood(st, now) + elif now >= st["flood"]["until"]: + st["flood"] = None + else: + if random.random() < FLOOD_EVENT_PROB: + area = st["flood"]["area"] + path = _path("%s/%s" % (area, random.choice(BASELINE[area]))) + # During a flood, alarms clear fast and may re-trigger, inflating the + # event rate the way a real upset does. Overwrite any pending clear. + writes.append((path, True)) + st["clears"][path] = now + random.uniform(10, 45) + + _write(writes) + + +def _start_flood(st, now): + area = random.choice(list(BASELINE.keys())) + st["flood"] = {"area": area, "until": now + FLOOD_DURATION} + system.util.getLogger("alarmsim").info("Flood burst started in area: %s" % area) + + +def force_flood(): + """Start a flood burst immediately (handy for demo/testing).""" + _start_flood(_state(), time.time()) + + +def reset(): + """Clear all simulation tags and internal state.""" + g = system.util.getGlobals() + g.pop("buildathon_alarmsim", None) + paths = [] + for area, names in BASELINE.items(): + paths += [_path("%s/%s" % (area, n)) for n in names] + paths += [_path(p) for p in CHATTER + STANDING + FLEETING] + system.tag.writeBlocking(paths, [False] * len(paths)) + + +def run_console(minutes=30): + """Script Console runner: calls tick() every 5 s for `minutes`. Blocks the console.""" + end = time.time() + minutes * 60 + while time.time() < end: + tick() + time.sleep(5) diff --git a/ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/resource.json b/ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/resource.json new file mode 100644 index 0000000..e160ebe --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/script-python/alarmsim/resource.json @@ -0,0 +1,12 @@ +{ + "scope": "A", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "code.py" + ], + "attributes": { + "hintScope": 2 + } +} diff --git a/ignition/gateway/projects/SimHarness/ignition/script-python/probe/code.py b/ignition/gateway/projects/SimHarness/ignition/script-python/probe/code.py new file mode 100644 index 0000000..56d2618 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/script-python/probe/code.py @@ -0,0 +1,179 @@ +# probe - gateway-side API ground truth (SimHarness, dev-only, Jython 2.7). +# +# Runs inside the gateway via the ProbeTick timer (enable it, scan, wait <=20s, +# read the output, disable it). Writes JSON to the bind-mounted repo path: +# container: /usr/local/bin/ignition/data/projects/.probe/out.json +# host repo: ignition/gateway/projects/.probe/out.json +# +# Edit PROBE_NAME / add sections as needed; every section is try-wrapped so a +# single API surprise never kills the whole probe. + +import json + +OUT_DIR = "/usr/local/bin/ignition/data/projects/.probe" +PROBE_NAME = "P0" + + +def _safe(fn): + # Bare except is deliberate: in Jython, JAVA exceptions (e.g. bad kwargs + # into system.* calls) do NOT subclass Python's Exception and would fly + # straight past 'except Exception'. + try: + return {"ok": True, "value": fn()} + except: + import sys + et, ev = sys.exc_info()[0], sys.exc_info()[1] + return {"ok": False, "error": "%s: %s" % (getattr(et, "__name__", et), ev)} + + +def _write(result): + from java.io import File + File(OUT_DIR).mkdirs() + f = open(OUT_DIR + "/out.json", "w") + try: + f.write(json.dumps(result, indent=1, default=lambda o: repr(o))) + finally: + f.close() + + +def _prop(evt, names): + out = {} + for n in names: + try: + v = evt.get(n) + out[n] = {"cls": v is not None and v.__class__.__name__ or None, + "repr": repr(v)[:200]} + except Exception, e: + out[n] = {"error": "%s" % e} + return out + + +def _entry_facts(evt): + d = {} + d["cls"] = evt.__class__.__name__ + try: + d["id"] = str(evt.getId()) + except Exception, e: + d["id_error"] = "%s" % e + try: + s = evt.getState() + d["state_str"] = str(s) + d["state_cls"] = s is not None and s.__class__.__name__ or None + except Exception, e: + d["state_error"] = "%s" % e + try: + d["source"] = str(evt.getSource()) + except Exception, e: + d["source_error"] = "%s" % e + try: + d["displayPath"] = repr(evt.getDisplayPath()) + except Exception, e: + d["displayPath_error"] = "%s" % e + try: + p = evt.getPriority() + d["priority_cls"] = p is not None and p.__class__.__name__ or None + d["priority_str"] = str(p) + try: + d["priority_int"] = int(p.ordinal()) + except Exception: + try: + d["priority_int"] = int(p) + except Exception: + d["priority_int"] = None + except Exception, e: + d["priority_error"] = "%s" % e + d["props"] = _prop(evt, ["eventTime", "eventType", "eventtype", "ackUser", + "ackUserName", "isSystemEvent", "systemEvent", + "eventValue", "name", "label"]) + for getter in ("getActiveData", "getClearedData", "getAckData"): + try: + v = getattr(evt, getter)() + d[getter] = v is not None and repr(v)[:200] or None + except Exception, e: + d[getter] = "ERR %s" % e + try: + from java.util import Date + t = evt.get("eventTime") + if t is not None and isinstance(t, Date): + d["eventTime_ms"] = t.getTime() + except Exception: + pass + return d + + +def _p0(): + result = {} + end = system.date.now() + start = system.date.addHours(end, -8) + + def q_basic(): + r = system.alarm.queryJournal(startDate=start, endDate=end) + entries = [e for e in r] + by_id = {} + for e in entries: + try: + k = str(e.getId()) + except Exception: + k = "?" + by_id.setdefault(k, []).append(e) + multi = {} + n = 0 + for k, evts in by_id.items(): + if len(evts) > 1 and n < 5: + states = [] + for e in evts: + try: + states.append(str(e.getState())) + except Exception: + states.append("?") + multi[k] = states + n += 1 + return { + "result_cls": r.__class__.__name__, + "len_works": _safe(lambda: len(r)), + "dataset_attr": hasattr(r, "getDataset"), + "entry_count": len(entries), + "distinct_ids": len(by_id), + "multi_state_id_samples": multi, + "first_entries": [_entry_facts(e) for e in entries[:6]], + "system_rows": [_entry_facts(e) for e in entries + if str(_safe(lambda: str(e.getSource()))["value"]).startswith("evt:")][:3], + } + result["basic"] = _safe(q_basic) + + result["no_kwargs_at_all"] = _safe( + lambda: len([e for e in system.alarm.queryJournal( + startDate=start, endDate=end)])) + result["with_journal_name"] = _safe( + lambda: len([e for e in system.alarm.queryJournal( + journalName="Journal", startDate=start, endDate=end)])) + result["bad_journal_name"] = _safe( + lambda: len([e for e in system.alarm.queryJournal( + journalName="NoSuchJournal", startDate=start, endDate=end)])) + result["long_millis_dates"] = _safe( + lambda: len([e for e in system.alarm.queryJournal( + startDate=system.date.toMillis(start), + endDate=system.date.toMillis(end))])) + result["include_system_false"] = _safe( + lambda: len([e for e in system.alarm.queryJournal( + startDate=start, endDate=end, includeSystemEvents=False)])) + result["include_system_false_alt"] = _safe( + lambda: len([e for e in system.alarm.queryJournal( + startDate=start, endDate=end, isSystem=False)])) + + def q_status(): + r = system.alarm.queryStatus(state=["ActiveUnacked", "ActiveAcked"]) + entries = [e for e in r] + return {"count": len(entries), + "first_entries": [_entry_facts(e) for e in entries[:5]]} + result["status"] = _safe(q_status) + result["shelved"] = _safe(lambda: repr(system.alarm.getShelvedPaths())) + return result + + +def run(): + result = {"probe": PROBE_NAME, + "ranAt": str(system.date.now())} + result["payload"] = _safe(_p0) + _write(result) + system.util.getLogger("probe").info("probe %s written to %s" % (PROBE_NAME, OUT_DIR)) diff --git a/ignition/gateway/projects/SimHarness/ignition/script-python/probe/resource.json b/ignition/gateway/projects/SimHarness/ignition/script-python/probe/resource.json new file mode 100644 index 0000000..e160ebe --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/script-python/probe/resource.json @@ -0,0 +1,12 @@ +{ + "scope": "A", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "code.py" + ], + "attributes": { + "hintScope": 2 + } +} diff --git a/ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/handleTimerEvent.py b/ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/handleTimerEvent.py new file mode 100644 index 0000000..fdc2e92 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/handleTimerEvent.py @@ -0,0 +1,2 @@ +def handleTimerEvent(): + alarmsim.tick() diff --git a/ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/resource.json b/ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/resource.json new file mode 100644 index 0000000..c5b6a70 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/timer/AlarmSimTick/resource.json @@ -0,0 +1,15 @@ +{ + "scope": "G", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "handleTimerEvent.py" + ], + "attributes": { + "sharedThread": false, + "delay": 5000, + "fixedDelay": true, + "enabled": true + } +} diff --git a/ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/handleTimerEvent.py b/ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/handleTimerEvent.py new file mode 100644 index 0000000..5c41195 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/handleTimerEvent.py @@ -0,0 +1,2 @@ +def handleTimerEvent(): + probe.run() diff --git a/ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/resource.json b/ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/resource.json new file mode 100644 index 0000000..1b34bba --- /dev/null +++ b/ignition/gateway/projects/SimHarness/ignition/timer/ProbeTick/resource.json @@ -0,0 +1,15 @@ +{ + "scope": "G", + "version": 1, + "restricted": false, + "overridable": true, + "files": [ + "handleTimerEvent.py" + ], + "attributes": { + "sharedThread": false, + "delay": 15000, + "fixedDelay": true, + "enabled": false + } +} diff --git a/ignition/gateway/projects/SimHarness/project.json b/ignition/gateway/projects/SimHarness/project.json new file mode 100644 index 0000000..778bb16 --- /dev/null +++ b/ignition/gateway/projects/SimHarness/project.json @@ -0,0 +1,7 @@ +{ + "title": "SimHarness", + "description": "Dev-only: alarm simulator + gateway probes + scratch views. NOT part of the Build-a-Thon submission.", + "enabled": true, + "inheritable": false, + "parent": "" +} diff --git a/tools/lint_project.py b/tools/lint_project.py new file mode 100644 index 0000000..b39f5d0 --- /dev/null +++ b/tools/lint_project.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +"""lint_project.py — deterministic contest/structure linter for the PrimeBAT project. + +Checks (stdlib only, exit non-zero on any FAIL): + structure every .json parses; resource.json integrity (declared files exist, + no undeclared payloads, scope correct); no .resources authored + refs embedded view paths + flex-repeater paths resolve to real views + under PrimeControls/; style-class references resolve + bindings binding types from the known set; property-binding paths well-formed; + session.custom.* references resolve against session-props schema; + view.params.* references resolve against the view's declared params; + script transforms/events are tab-indented + contest page-config: exactly one page "/" -> PrimeControls/Dashboard, empty + sharedDocks; all view/style resources under PrimeControls/; + NO tag bindings anywhere in PrimeBAT; string bans (hardcoded local + paths/datasource/simulator/SQL/f-strings/journalName literal); + queryJournal calls bounded by start+end + --strict (Phase 4) additionally: no ignition/* resources besides + script-python/PrimeControls/* and global-props; session custom keys + only PrimeControls; dead view params reported as failures + +Usage: python3 tools/lint_project.py [--strict] [--project PrimeBAT] +""" +import argparse +import json +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +PERSPECTIVE = "com.inductiveautomation.perspective" + +KNOWN_BINDING_TYPES = {"property", "expr", "expression", "tag", "query", "expr-struct", "http"} +EMBED_TYPES = {"ia.display.view", "ia.display.flex-repeater"} + +# (regex, message) — scanned over view.json raw text and PrimeControls *.py +STRING_BANS = [ + (re.compile(r"\[default\]"), "hardcoded tag provider path '[default]'"), + (re.compile(r"BuildathonSim"), "hardcoded simulator tag path 'BuildathonSim'"), + (re.compile(r"Buildathon_DB"), "hardcoded local datasource 'Buildathon_DB'"), + (re.compile(r"\balarmsim\b"), "reference to dev simulator 'alarmsim'"), + (re.compile(r"journalName\s*="), "hardcoded journalName= (judges' journal name unknown)"), + (re.compile(r"\bsystem\.db\."), "system.db.* SQL access (journal API only)"), + (re.compile(r"\bSELECT\s+", re.IGNORECASE), "raw SQL SELECT"), + (re.compile(r"""(^|[^A-Za-z0-9_])[fF]["']"""), "f-string (Jython 2.7 cannot parse)"), + (re.compile(r"system\.util\.sendRequest\("), "sendRequest dev bridge leftover"), +] + +FAILS = [] +WARNS = [] + + +def fail(path, msg): + FAILS.append("FAIL %s: %s" % (path, msg)) + + +def warn(path, msg): + WARNS.append("warn %s: %s" % (path, msg)) + + +def rel(p): + return os.path.relpath(p, ROOT) + + +def load_json(path): + try: + with open(path) as f: + return json.load(f) + except Exception as e: + fail(rel(path), "invalid JSON: %s" % e) + return None + + +def walk_components(node, visit, path="root"): + """Depth-first over a view component tree; visit(node, path).""" + if not isinstance(node, dict): + return + visit(node, path) + for i, child in enumerate(node.get("children") or []): + name = "?" + if isinstance(child, dict): + name = (child.get("meta") or {}).get("name", "?") + walk_components(child, visit, "%s/%s[%d]" % (path, name, i)) + + +def iter_prop_configs(view): + """Yield (owner_path, prop_key, config_dict) for view-level and component-level propConfig.""" + for k, v in (view.get("propConfig") or {}).items(): + yield ("view", k, v) + collected = [] + + def visit(node, path): + for k, v in (node.get("propConfig") or {}).items(): + collected.append((path, k, v)) + walk_components(view.get("root") or {}, visit) + for item in collected: + yield item + + +def resolve_in_schema(schema, dotted): + """Walk a dict tree by dotted path; list-typed nodes accept any index; return True if resolvable.""" + cur = schema + for part in dotted.split("."): + part = part.split("[")[0] + if isinstance(cur, list): + return True # element access into arrays: shape unknown, accept + if not isinstance(cur, dict) or part not in cur: + return False + cur = cur[part] + return True + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--strict", action="store_true") + ap.add_argument("--project", default="PrimeBAT") + args = ap.parse_args() + + proj = os.path.join(ROOT, "ignition", "gateway", "projects", args.project) + if not os.path.isdir(proj): + print("project dir not found: %s" % proj) + sys.exit(2) + + views_root = os.path.join(proj, PERSPECTIVE, "views") + styles_root = os.path.join(proj, PERSPECTIVE, "style-classes") + + # ---------- collect resource inventory ---------- + view_paths = set() # e.g. "PrimeControls/Dashboard" + style_paths = set() # e.g. "PrimeControls/Card" + for base, target, payload in ((views_root, view_paths, "view.json"), + (styles_root, style_paths, "style.json")): + if not os.path.isdir(base): + continue + for dirpath, _dirs, files in os.walk(base): + if payload in files: + target.add(os.path.relpath(dirpath, base).replace(os.sep, "/")) + + # ---------- structure: parse every json + resource.json integrity ---------- + session_schema = {} + for dirpath, dirs, files in os.walk(proj): + if ".resources" in dirs: + fail(rel(dirpath), "authored .resources directory (gateway-owned)") + dirs.remove(".resources") + for fn in files: + if fn.endswith(".json"): + load_json(os.path.join(dirpath, fn)) + if "resource.json" in files: + res = load_json(os.path.join(dirpath, "resource.json")) + if res is None: + continue + declared = res.get("files") or [] + payloads = [f for f in files if f != "resource.json"] + for f in declared: + if f not in payloads: + fail(rel(dirpath), "resource.json declares missing file %r" % f) + for f in payloads: + if f not in declared: + fail(rel(dirpath), "undeclared payload file %r" % f) + rp = rel(dirpath) + want_a = ("script-python" in rp) or ("global-props" in rp) + want_scope = "A" if want_a else "G" + if res.get("scope") != want_scope: + fail(rp, "scope %r, expected %r" % (res.get("scope"), want_scope)) + + # ---------- session props schema ---------- + sp_path = os.path.join(proj, PERSPECTIVE, "session-props", "props.json") + sp = load_json(sp_path) if os.path.exists(sp_path) else None + if sp: + session_schema = sp.get("custom") or {} + if args.strict: + for key in session_schema: + if key != "PrimeControls": + fail(rel(sp_path), "session custom key %r outside PrimeControls" % key) + + # ---------- page-config contest rules ---------- + pc_path = os.path.join(proj, PERSPECTIVE, "page-config", "config.json") + pc = load_json(pc_path) if os.path.exists(pc_path) else None + if pc is not None: + pages = pc.get("pages") or {} + if list(pages.keys()) != ["/"]: + fail(rel(pc_path), "pages must be exactly ['/'], got %s" % list(pages.keys())) + elif (pages["/"] or {}).get("viewPath") != "PrimeControls/Dashboard": + fail(rel(pc_path), "page '/' must map to PrimeControls/Dashboard") + if pc.get("sharedDocks"): + fail(rel(pc_path), "sharedDocks must be empty (docked views prohibited)") + + # ---------- namespace rule: everything under PrimeControls/ ---------- + for p in sorted(view_paths): + if not p.startswith("PrimeControls/"): + fail("views/%s" % p, "view outside PrimeControls/ folder") + for p in sorted(style_paths): + if not p.startswith("PrimeControls/"): + fail("style-classes/%s" % p, "style class outside PrimeControls/ folder") + + # ---------- per-view checks ---------- + for vp in sorted(view_paths): + vfile = os.path.join(views_root, vp.replace("/", os.sep), "view.json") + view = load_json(vfile) + if view is None: + continue + rv = rel(vfile) + declared_params = set((view.get("params") or {}).keys()) + raw = open(vfile).read() + + for rx, msg in STRING_BANS: + if rx.search(raw): + fail(rv, msg) + + def visit(node, cpath): + t = node.get("type") + if t in EMBED_TYPES: + path_prop = (node.get("props") or {}).get("path") + bound = "props.path" in (node.get("propConfig") or {}) + if isinstance(path_prop, str) and path_prop: + if path_prop not in view_paths: + fail(rv, "%s embeds missing view %r" % (cpath, path_prop)) + elif not path_prop.startswith("PrimeControls/"): + fail(rv, "%s embeds non-PrimeControls view %r" % (cpath, path_prop)) + elif not bound: + warn(rv, "%s embed with no static path and no binding" % cpath) + classes = ((node.get("props") or {}).get("style") or {}).get("classes") + if isinstance(classes, str): + for cls in classes.split(): + if cls not in style_paths: + fail(rv, "%s references missing style class %r" % (cpath, cls)) + walk_components(view.get("root") or {}, visit) + + for owner, key, cfg in iter_prop_configs(view): + binding = (cfg or {}).get("binding") + if not binding: + continue + btype = binding.get("type") + if btype not in KNOWN_BINDING_TYPES: + fail(rv, "%s %s: unknown binding type %r" % (owner, key, btype)) + if btype == "tag": + fail(rv, "%s %s: tag binding (prohibited in PrimeBAT — journal API only)" % (owner, key)) + text_blobs = [json.dumps(binding.get("config") or {})] + for tr in binding.get("transforms") or []: + code = tr.get("code", "") + text_blobs.append(code) + if tr.get("type") == "script" and code and not code.startswith(("\t", "\n")): + fail(rv, "%s %s: script transform not tab-indented" % (owner, key)) + blob = " ".join(text_blobs) + for m in re.finditer(r"session\.custom\.([A-Za-z0-9_.\[\]]+)", blob): + if not resolve_in_schema(session_schema, m.group(1)): + fail(rv, "%s %s: unresolvable session.custom.%s" % (owner, key, m.group(1))) + for m in re.finditer(r"view\.params\.([A-Za-z0-9_]+)", blob): + if m.group(1) not in declared_params: + fail(rv, "%s %s: undeclared view param %r" % (owner, key, m.group(1))) + + if args.strict: + for p in declared_params: + if not re.search(r"(view\.params\.|params\.)%s" % re.escape(p), raw.replace('"%s"' % p, "", 1)): + warn(rv, "param %r possibly unused" % p) + + # ---------- scripts: string bans + bounded queries ---------- + sp_root = os.path.join(proj, "ignition", "script-python") + if os.path.isdir(sp_root): + for dirpath, _dirs, files in os.walk(sp_root): + for fn in files: + if not fn.endswith(".py"): + continue + fpath = os.path.join(dirpath, fn) + src = open(fpath).read() + for rx, msg in STRING_BANS: + if rx.search(src): + fail(rel(fpath), msg) + for m in re.finditer(r"queryJournal\s*\(", src): + fstart = src.rfind("\ndef ", 0, m.start()) + fend = src.find("\ndef ", m.start()) + body = src[max(fstart, 0):fend if fend != -1 else len(src)] + if not (re.search(r"start", body) and re.search(r"end", body)): + fail(rel(fpath), "queryJournal call not visibly bounded by start/end") + + # ---------- strict: no stray ignition resources ---------- + if args.strict: + ig_root = os.path.join(proj, "ignition") + for entry in sorted(os.listdir(ig_root)) if os.path.isdir(ig_root) else []: + if entry not in ("script-python", "global-props"): + fail("ignition/%s" % entry, "unexpected project resource type for submission") + if os.path.isdir(sp_root): + for entry in sorted(os.listdir(sp_root)): + if entry != "PrimeControls": + fail("script-python/%s" % entry, "script package outside PrimeControls") + + for w in WARNS: + print(w) + for f in FAILS: + print(f) + print("lint: %d view(s), %d style class(es), %d failure(s), %d warning(s)%s" + % (len(view_paths), len(style_paths), len(FAILS), len(WARNS), + " [strict]" if args.strict else "")) + sys.exit(1 if FAILS else 0) + + +if __name__ == "__main__": + main() diff --git a/tools/schema_harvest.sh b/tools/schema_harvest.sh new file mode 100755 index 0000000..26edd69 --- /dev/null +++ b/tools/schema_harvest.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# schema_harvest.sh — extract Perspective component prop schemas from the running +# gateway's Perspective module into per-component JSON Schema files. +# +# Sources (verified on 8.3.7): perspective-common-.jar inside +# Perspective-module.modl carries ia.components.json (70 core components), +# perspective-amcharts.components.json (ia.chart.xy/pie/gauge/simple-gauge), +# perspective-timeseries.components.json, plus schemas/*.json (binding, +# transform, style, view-props, session-props schemas). +# +# Output layout: +# /components/.schema.json (the component's props JSON Schema) +# /schemas/.json (binding/transform/etc. schemas) +# +# Usage: tools/schema_harvest.sh [output_dir] (default: .schemas/) +set -euo pipefail +OUT="${1:-.schemas}" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +docker cp buildathon-ignition:"/usr/local/bin/ignition/user-lib/modules/Perspective-module.modl" "$TMP/perspective.modl" + +python3 - "$TMP" "$OUT" <<'EOF' +import json, os, sys, zipfile +tmp, out = sys.argv[1], sys.argv[2] +comp_dir = os.path.join(out, "components") +misc_dir = os.path.join(out, "schemas") +os.makedirs(comp_dir, exist_ok=True) +os.makedirs(misc_dir, exist_ok=True) + +modl = zipfile.ZipFile(os.path.join(tmp, "perspective.modl")) +total = 0 +for jar in [n for n in modl.namelist() if n.endswith(".jar")]: + jp = os.path.join(tmp, os.path.basename(jar)) + open(jp, "wb").write(modl.read(jar)) + try: + z = zipfile.ZipFile(jp) + except zipfile.BadZipFile: + continue + for entry in z.namelist(): + if entry.endswith(".components.json") or entry == "ia.components.json": + data = json.loads(z.read(entry)) + for comp in data.get("components", []): + cid = comp.get("id") + if not cid: + continue + with open(os.path.join(comp_dir, cid + ".schema.json"), "w") as f: + json.dump(comp.get("schema", {}), f, indent=1) + total += 1 + elif entry.startswith("schemas/") and entry.endswith(".json"): + with open(os.path.join(misc_dir, os.path.basename(entry)), "wb") as f: + f.write(z.read(entry)) +print("extracted %d component schemas -> %s" % (total, comp_dir)) +EOF +ls "$OUT/components" | wc -l