Compare commits

..

164 Commits

Author SHA1 Message Date
Mikayla
b96eb7d89d Merge pull request #492 from MikaylaFischler/devel
2024.05.16 Beta Hotfix
2024-05-16 22:13:01 -04:00
Mikayla Fischler
9b8947fba2 #491 fixed ps table indexing for boiler/turbine online 2024-05-16 22:06:53 -04:00
Mikayla
afc38e7e7a Merge pull request #490 from MikaylaFischler/devel
2024.05.15 Release
2024-05-15 17:22:18 -04:00
Mikayla Fischler
41b7a68f3e #487 stop retrying failed disable when needing to enable 2024-05-14 20:10:21 -04:00
Mikayla Fischler
8968ebede3 #486 fixed pcall messages for newer CC versions 2024-05-14 20:00:34 -04:00
Mikayla
50a9168b06 Merge pull request #489 from MikaylaFischler/pocket-alpha-dev
Pocket Update
2024-05-12 18:27:03 -04:00
Mikayla Fischler
76e85da9d5 version increments and small fix 2024-05-12 18:19:21 -04:00
Mikayla Fischler
be560cd532 luacheck fixes 2024-05-12 15:19:01 -04:00
Mikayla Fischler
2b0a536292 #200 pocket RCS overview 2024-05-12 15:14:58 -04:00
Mikayla Fischler
afed6f514d removed stray space in annunciator Coolant Level Low 2024-05-12 15:05:36 -04:00
Mikayla Fischler
3e6a0a8869 #200 updated RPS indicator text 2024-05-12 14:20:48 -04:00
Mikayla Fischler
b99cf19be0 #200 placeholder for alarm page, start of RCS page 2024-05-12 13:36:46 -04:00
Mikayla Fischler
7f009f9c86 #200 RPS view on pocket 2024-05-12 13:28:34 -04:00
Mikayla Fischler
6a8ed311f3 #200 functioning pocket unit overview 2024-05-11 19:19:52 -04:00
Mikayla Fischler
c181142f75 added network details to about app 2024-05-11 15:03:14 -04:00
Mikayla Fischler
0cb964a177 diagnostic disables 2024-05-10 19:18:21 -04:00
Mikayla Fischler
3cd832ca20 sidebar updates 2024-05-10 19:17:52 -04:00
Mikayla Fischler
0c6c7bf9a5 #200 work in progress unit views and sidebar/app updates 2024-05-09 23:05:55 -04:00
Mikayla Fischler
3bfcb1d83c #485 fixed assertion with height auto incrementing y when inheriting height 2024-05-04 13:50:53 -04:00
Mikayla
4fe6792804 Merge pull request #483 from MikaylaFischler/devel
2024.04.30 Release
2024-04-30 20:35:24 -04:00
Mikayla Fischler
25dc47d520 fixed recording bad stats on induction matrix faults 2024-04-30 20:28:07 -04:00
Mikayla Fischler
f958b0e3b7 fixed at max i/o indicator 2024-04-30 20:27:04 -04:00
Mikayla Fischler
f621ff2482 added some value inits and unit labels 2024-04-30 18:54:01 -04:00
Mikayla Fischler
eb45ff899b #455 calculate reactor temp high limit 2024-04-29 22:03:54 -04:00
Mikayla
e91fd2fcaa Merge pull request #482 from MikaylaFischler/412-additional-matrix-integrations
412 additional matrix integrations
2024-04-28 13:08:51 -04:00
Mikayla Fischler
d35b824458 luacheck fix and cleanup 2024-04-28 13:08:16 -04:00
Mikayla Fischler
165d1497f8 reverted test change that got committed 2024-04-28 02:01:40 -04:00
Mikayla Fischler
50bf057ca6 #412 optionally disable SPS at low power 2024-04-28 02:01:21 -04:00
Mikayla Fischler
6f768ef6b3 #469 made ETA tolerant to induction matrix capacity changes 2024-04-28 01:26:44 -04:00
Mikayla Fischler
826086951e return zero on mov_avg compute if no samples 2024-04-27 19:50:35 -04:00
Mikayla Fischler
35bf56663f #469 induction matrix charge ETAs and misc cleanup/updates 2024-04-27 16:27:01 -04:00
Mikayla Fischler
7b8cea4a5c Merge branch 'devel' into 412-additional-matrix-integrations 2024-04-21 13:57:47 -04:00
Mikayla Fischler
51d4a22532 #478 simplified reactor PLC reactor formed handling 2024-04-21 13:55:39 -04:00
Mikayla Fischler
fb85c2f05b RTU configurator updates for redstone I/O clarity 2024-04-21 13:54:14 -04:00
Mikayla Fischler
712d018806 Merge branch 'devel' into 412-additional-matrix-integrations 2024-04-21 12:09:53 -04:00
Mikayla Fischler
00a8d64a88 fixed coordinator not showing FAIL on unit count mismatch when connecting 2024-04-20 20:38:55 -04:00
Mikayla Fischler
d9efd5b8d2 #412 updates to RSIO for induction matrix low, high, and analog charge level 2024-04-20 16:32:18 -04:00
Mikayla Fischler
a786404092 #476 fixed PPM not initing fault counter in __index handler when a function is found 2024-04-16 15:55:40 -04:00
Mikayla
1bd03c0b1a Merge pull request #473 from MikaylaFischler/devel
2024.04.14 Release
2024-04-15 11:59:40 -04:00
Mikayla Fischler
0d8d7aeb15 readme updates 2024-04-15 10:20:48 -04:00
Mikayla Fischler
0b60dc9fa4 #474 run main reactor-plc loop clock when not networked 2024-04-14 19:24:12 -04:00
Mikayla Fischler
48b91ac808 fixed reactor-plc configurator auth key displaying *** when not networked 2024-04-14 19:08:19 -04:00
Mikayla Fischler
4c9efc78b1 bumped up scada-common version 2024-04-14 16:30:50 -04:00
Mikayla
3c5857cd68 Merge pull request #472 from MikaylaFischler/pocket-alpha-dev
Work on Pocket Alpha App
2024-04-14 16:17:14 -04:00
Mikayla Fischler
473b0dd10d fixes and comments 2024-04-14 16:16:18 -04:00
Mikayla Fischler
d2df7db18b luacheck fixes 2024-04-14 15:30:13 -04:00
Mikayla Fischler
0ca002316f work on pocket, bumped comms version for PR 2024-04-14 15:28:38 -04:00
Mikayla Fischler
9fe34648c2 added system information/about app 2024-04-13 16:18:27 -04:00
Mikayla Fischler
57dc20692b need to use a compact signal bar due to how edge extensions work 2024-04-13 16:15:20 -04:00
Mikayla Fischler
f22d699baf flipped app pane scroll direction 2024-04-13 16:14:38 -04:00
Mikayla Fischler
23b31e0049 #410 pocket nav overhaul 2024-04-13 14:47:20 -04:00
Mikayla Fischler
2b4309afa7 pocket coordinator linking fixes 2024-04-13 11:02:41 -04:00
Mikayla
89cd5a07f2 Merge branch 'pocket-alpha-dev' of https://github.com/MikaylaFischler/cc-mek-scada into pocket-alpha-dev 2024-04-13 00:42:01 +00:00
Mikayla
99213da760 #200 work on pocket comms for unit data 2024-04-13 00:41:47 +00:00
Mikayla Fischler
f23f69d441 Merge branch 'devel' into pocket-alpha-dev 2024-04-12 20:27:21 -04:00
Mikayla
a99b7912f8 Merge pull request #471 from MikaylaFischler/366-charge-control-mode-idling
366 charge control mode idling
2024-04-12 20:07:21 -04:00
Mikayla Fischler
23ca5fb69e clear flow monitor on coordinator ui close 2024-04-12 20:06:39 -04:00
Mikayla Fischler
c243d064ef fixed incorrect render behavior on quick supervisor reconnects 2024-04-12 00:18:35 -04:00
Mikayla Fischler
878c3b92e1 #366 added idling to config and adjusted/fixed some behaviors 2024-04-12 00:13:05 -04:00
Mikayla Fischler
2aa52d2e2c simplified a config validation assert 2024-04-11 20:30:54 -04:00
Mikayla Fischler
dfc1ee6497 cleanup and constants 2024-04-10 22:16:41 -04:00
Mikayla Fischler
a6a1a61954 #470 reworked flow stability logic 2024-04-10 21:30:51 -04:00
Mikayla Fischler
d0b50c834c fixed coordinator not exiting on failed connection 2024-04-10 20:49:14 -04:00
Mikayla Fischler
612a06ba98 use os.clock rather than unix time for control 2024-04-09 22:24:03 -04:00
Mikayla Fischler
65d43d55c7 #366 idling and charge level PD gain changes 2024-04-09 22:23:43 -04:00
Mikayla Fischler
4fcd375ee2 show average charge rather than current charge below charge target 2024-04-09 22:20:46 -04:00
Mikayla Fischler
a22f5562cf only ramp up on reactor plc 2024-04-09 22:19:53 -04:00
Mikayla
0365ea5e8a Merge branch 'pocket-alpha-dev' of https://github.com/MikaylaFischler/cc-mek-scada into pocket-alpha-dev 2024-04-09 14:27:50 +00:00
Mikayla
da300607f1 Merge pull request #468 from MikaylaFischler/460-create-coordinator-ui-thread
460 create coordinator UI thread
2024-04-09 00:10:44 -04:00
Mikayla Fischler
cc3174ee76 comments and whitespace 2024-04-09 00:08:47 -04:00
Mikayla Fischler
98c37caecd #460 cleanup 2024-04-08 23:59:16 -04:00
Mikayla Fischler
cc50e4a020 #460 removed coordinator connecting grace period 2024-04-07 22:33:48 -04:00
Mikayla Fischler
f734c4307b #460 exit on UI crash 2024-04-07 20:55:07 -04:00
Mikayla Fischler
45573be8c7 #460 renderer logging 2024-04-07 20:47:31 -04:00
Mikayla Fischler
eab1ffbe03 #460 cleanup and moved date and time update to be behind UI ready rather than linked 2024-04-07 20:24:27 -04:00
Mikayla Fischler
1504247b33 #460 added yields throughout main UI rendering 2024-04-07 20:21:57 -04:00
Mikayla Fischler
92a4277f73 #460 moved connect/disconnect/resize to render thread 2024-04-07 19:54:22 -04:00
Mikayla Fischler
31a663e86b #460 threaded coordinator and moved UI start to render thread 2024-04-07 19:37:06 -04:00
Mikayla Fischler
5c0e2c32ee #461 prevent log spam in standalone mode when RPS trips continuously 2024-04-07 18:04:11 -04:00
Mikayla
3537c59365 Merge pull request #466 from MikaylaFischler/devel
2024.03.31 Release
2024-03-31 18:53:18 -04:00
Mikayla Fischler
1d7104ae74 Merge branch 'devel' into pocket-alpha-dev 2024-03-31 13:35:23 -04:00
Mikayla Fischler
4629e1ba2a #464 fixed color options button not appearing disabled when disabled 2024-03-31 00:03:28 -04:00
Mikayla Fischler
659644865a #449 include disconnected configured monitors in monitor list 2024-03-30 12:46:01 -04:00
Mikayla Fischler
03d2b3f087 #452 enable emergency coolant on boiler water level low when reactor can't cool 2024-03-29 18:31:17 -04:00
Mikayla
bea7b91ff1 Merge pull request #459 from MikaylaFischler/457-colorblind-independent-color-accessibility-modifiers
457 colorblind independent color accessibility modifiers
2024-03-25 10:13:15 -04:00
Mikayla Fischler
b94c89f4ec #457 cleanup 2024-03-25 10:11:35 -04:00
Mikayla Fischler
55e4e5a68b #457 fix for indicator background 2024-03-24 23:05:52 -04:00
Mikayla Fischler
e1ad76a00d #457 fixes and adjusted text 2024-03-24 13:56:19 -04:00
Mikayla Fischler
93e4590947 #457 added standard with black off 2024-03-24 13:39:24 -04:00
Mikayla Fischler
2442e7f972 #457 added ind_bkg for front panels 2024-03-24 13:03:48 -04:00
Mikayla Fischler
bb2c07963b #457 work on blue indicator modes 2024-03-24 12:56:51 -04:00
Mikayla Fischler
44c6352a8c #458 changed induction matrix title text to be white in dark mode 2024-03-23 01:15:42 -04:00
Mikayla Fischler
968b0a9122 log environment versions when debug logs are enabled 2024-03-23 00:49:19 -04:00
Mikayla Fischler
19869416af #434 #454 PPM improvements and undefined function overhaul 2024-03-23 00:26:58 -04:00
Mikayla
598f6f08af Merge branch 'devel' into pocket-alpha-dev 2024-03-13 13:55:51 +00:00
Mikayla
ca2d8ab7da Merge pull request #450 from MikaylaFischler/devel
2024.03.12 Release
2024-03-12 21:15:24 -04:00
Mikayla Fischler
1c0f61b3e0 changed config defaults to use theme enums 2024-03-12 21:09:37 -04:00
Mikayla Fischler
ecd3575643 Merge branch 'color-update' into devel 2024-03-12 13:35:39 -04:00
Mikayla Fischler
9dc3a09f4d bumped up pocket version for configurator change 2024-03-12 13:35:15 -04:00
Mikayla
7a5d14d67f Merge pull request #448 from MikaylaFischler/color-update
Colorblind Modes
2024-03-12 13:25:36 -04:00
Mikayla Fischler
d6175e5cec switched to using LEDPair elements for colorblind mode network lights 2024-03-12 13:23:39 -04:00
Mikayla Fischler
a00a824a7f cleanup and fixes 2024-03-12 13:15:28 -04:00
Mikayla Fischler
9393632428 removed unused value specifiers 2024-03-12 12:54:31 -04:00
Mikayla Fischler
886bd0d5d5 luacheck fixes 2024-03-12 12:49:33 -04:00
Mikayla Fischler
9c1d83fdfc Merge branch 'devel' into color-update 2024-03-12 12:46:17 -04:00
Mikayla Fischler
bd88244681 #439 remind user to configure peripherals and redstone, and provide buttons to do so 2024-03-12 12:45:47 -04:00
Mikayla
8dae632b25 simplified checks for colorblind mode 2024-03-12 16:24:32 +00:00
Mikayla Fischler
89d56d3101 moved main UI palettes to themes.lua and set configurators to use it 2024-03-11 23:54:03 -04:00
Mikayla Fischler
1f451ff92a #340 coordinator colorblind support 2024-03-11 23:31:31 -04:00
Mikayla Fischler
9d08b51f84 #340 restored softer blue for deuteranopia/protanopia basalt theme 2024-03-11 21:43:11 -04:00
Mikayla Fischler
047ff5c203 Merge branch 'devel' into color-update 2024-03-11 21:26:23 -04:00
Mikayla Fischler
895f768e58 Merge branch 'color-update' of github.com:MikaylaFischler/cc-mek-scada into color-update 2024-03-11 21:25:55 -04:00
Mikayla Fischler
b3f29566ea #340 colorblind modes for rtu, reactor-plc, and supervisor 2024-03-11 21:25:34 -04:00
Mikayla
c0a5c8d504 Merge pull request #447 from MikaylaFischler/color-update
Dark Mode Themes
2024-03-11 12:38:22 -04:00
Mikayla
bbe7b52662 bugfixes and cleanup 2024-03-11 16:35:06 +00:00
Mikayla Fischler
d5b166dcc6 Merge branch 'devel' into color-update 2024-03-09 18:51:21 -05:00
Mikayla
b0aa6d54ac Merge pull request #446 from MikaylaFischler/devel
2024.03.09 Beta Hotfix
2024-03-09 18:14:02 -05:00
Mikayla Fischler
ad240ae44c #445 increment common version 2024-03-09 17:54:04 -05:00
Mikayla Fischler
79c93f1562 #445 fixed PPM undefined field logic and improved RTU unit fault handling 2024-03-09 13:24:06 -05:00
Mikayla Fischler
5d760a0524 #405 helper functions, enums, and name tables added to themes.lua 2024-03-09 12:41:45 -05:00
Mikayla Fischler
6c89b3134c #405 make pu fallback selector visible 2024-03-09 12:39:37 -05:00
Mikayla
3c7fff28c9 Merge pull request #443 from MikaylaFischler/devel
2024.03.08 Release
2024-03-08 22:40:28 -05:00
Mikayla Fischler
814043bf04 #405 basalt theme color adjustments 2024-03-07 20:46:10 -05:00
Mikayla Fischler
fc7896ebd3 #405 #340 rtu and supervisor configurator control of theme and color mode 2024-03-07 19:23:46 -05:00
Mikayla Fischler
48a8eadc55 #405 #340 reactor plc configurator control of theme and color mode 2024-03-07 18:00:33 -05:00
Mikayla
f0f2aadf53 #200 work on pocket comms 2024-03-07 17:27:25 +00:00
Mikayla
ce37a672a3 Merge branch 'devel' into pocket-alpha-dev 2024-03-07 16:21:37 +00:00
Mikayla Fischler
510995b04f #405 #340 coordinator configurator control of theme and color mode 2024-03-06 23:35:30 -05:00
Mikayla Fischler
560061d4ad removed latest shield and added missing common version shield 2024-03-06 20:51:23 -05:00
Mikayla Fischler
d87e3893f0 #405 plc and rtu front panel themes 2024-03-06 12:18:50 -05:00
Mikayla Fischler
fc198cd9d2 #405 supervisor and coordinator front panel themes 2024-03-06 11:43:31 -05:00
Mikayla Fischler
c714e49ad8 Merge branch 'devel' into color-update 2024-03-05 22:03:24 -05:00
Mikayla Fischler
d1e4ea586e supervisor comment cleanup 2024-03-05 21:47:14 -05:00
Mikayla Fischler
4e789ab92d cleanup and refactors 2024-03-05 21:24:17 -05:00
Mikayla Fischler
0892a57d35 #442 return rather than assert on configuration error 2024-03-05 20:17:52 -05:00
Mikayla Fischler
1bc4828010 #438 use reported polonium rate rather than an estimate 2024-03-05 19:35:54 -05:00
Mikayla Fischler
fb5a9d5d9e #432 fixes and enhancements to coordinator waiting on chunk loads 2024-03-05 17:16:35 -05:00
Mikayla Fischler
adbf1f2f78 #441 #431 bugfixes to the bugfixes 2024-03-05 17:12:12 -05:00
Mikayla Fischler
2f99aaeedb #431 handle ppm mount of unformed reactor race condition 2024-03-05 10:56:27 -05:00
Mikayla Fischler
a318ffb283 #405 styling improvements to PLC front panel 2024-03-05 10:51:18 -05:00
Mikayla Fischler
a677e994d6 Merge branch 'devel' into color-update 2024-02-26 14:44:47 -05:00
Mikayla Fischler
f9917b786c #432 wait 20s on computer power on before assuming monitor configuration problem 2024-02-25 18:02:13 -05:00
Mikayla Fischler
dbc1f41c5d #406 bounds check all controls 2024-02-25 15:53:14 -05:00
Mikayla Fischler
d6185e0183 #433 use os.clock instead of util.time_s for coordinator connection timeout to supervisor 2024-02-25 13:13:36 -05:00
Mikayla Fischler
6ef049baa1 #405 reverted yellow, desaturated orange 2024-02-25 13:09:30 -05:00
Mikayla Fischler
a4214e8a4f #405 fixed waste line still always being black and removed colormap test 2024-02-24 19:34:43 -05:00
Mikayla Fischler
45881067df fixed extra space in RCS indicator list 2024-02-24 18:01:30 -05:00
Mikayla Fischler
c40aa229bf #405 flow monitor theme implementation 2024-02-24 17:35:10 -05:00
Mikayla Fischler
51f2bba4d1 #405 theme implementation for unit displays 2024-02-24 15:37:39 -05:00
Mikayla Fischler
628a50e1bd #405 WIP themes and completed main display theme implementation 2024-02-24 14:35:04 -05:00
Mikayla Fischler
cdd31508d9 #430 fixed unit boiler, turbine, and tank status indicators flashing OFF-LINE when online 2024-02-22 21:35:08 -05:00
Mikayla
83d62991f8 Merge pull request #429 from MikaylaFischler/328-coordinator-temperature-unit-options
328 coordinator temperature unit options
2024-02-22 20:50:01 -05:00
Mikayla Fischler
f207a950e4 fixed bug with hmac still being used for connecting in coordinator configurator after clearing key 2024-02-22 19:25:55 -05:00
Mikayla Fischler
0b0051dc2f #328 K, C, F, and R temperature unit options 2024-02-22 19:25:16 -05:00
Mikayla Fischler
f152c37ea9 #387 handle resizing, improved reconnect handling, fixed disconnect detection bug 2024-02-21 20:33:07 -05:00
Mikayla Fischler
372fd426d8 test code for psil allocations 2024-02-21 18:48:55 -05:00
Mikayla Fischler
526a54903e #410 moved diagnostic apps to main app list and added app page nav 2024-01-15 17:40:43 -05:00
Mikayla Fischler
e1a632dcc7 Merge branch 'devel' into pocket-alpha-dev 2024-01-14 14:21:09 -05:00
Mikayla Fischler
d5d818e625 pocket signal bars 2024-01-14 14:20:59 -05:00
Mikayla Fischler
03de90c3d8 fixes to page navigation nav_up 2023-12-31 12:58:24 -05:00
Mikayla Fischler
99096e0fc9 Merge branch 'devel' into pocket-alpha-dev 2023-12-30 22:58:53 -05:00
Mikayla Fischler
0395aa95b6 indicate pocket app is a work in progress 2023-12-22 22:30:22 -05:00
Mikayla Fischler
c624baed2c #395 fixes to new pocket navigation 2023-12-22 12:46:36 -05:00
Mikayla
1a40321c0f #395 pocket navigation system 2023-12-22 16:12:47 +00:00
98 changed files with 5888 additions and 2277 deletions

View File

@@ -4,12 +4,11 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi
![GitHub](https://img.shields.io/github/license/MikaylaFischler/cc-mek-scada) ![GitHub](https://img.shields.io/github/license/MikaylaFischler/cc-mek-scada)
![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/MikaylaFischler/cc-mek-scada?include_prereleases) ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/MikaylaFischler/cc-mek-scada?include_prereleases)
![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/MikaylaFischler/cc-mek-scada/check.yml?branch=main&label=main) ![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/MikaylaFischler/cc-mek-scada/check.yml?branch=main&label=main)
![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/MikaylaFischler/cc-mek-scada/check.yml?branch=latest&label=latest)
![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/MikaylaFischler/cc-mek-scada/check.yml?branch=devel&label=devel) ![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/MikaylaFischler/cc-mek-scada/check.yml?branch=devel&label=devel)
### [Join](https://discord.gg/R9NSCkhcwt) the Discord! ### Join [the Discord](https://discord.gg/R9NSCkhcwt)!
![Discord](https://img.shields.io/discord/1129075839288496259) ![Discord](https://img.shields.io/discord/1129075839288496259?logo=Discord&logoColor=white&label=discord)
## Released Component Versions ## Released Component Versions
@@ -17,6 +16,7 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi
![Bootloader](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Fbootloader.json) ![Bootloader](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Fbootloader.json)
![Comms](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Fcommon.json)
![Comms](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Fcomms.json) ![Comms](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Fcomms.json)
![Graphics](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Fgraphics.json) ![Graphics](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Fgraphics.json)
![Lockbox](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Flockbox.json) ![Lockbox](https://img.shields.io/endpoint?url=https%3A%2F%2Fmikaylafischler.github.io%2Fcc-mek-scada%2Flockbox.json)
@@ -46,6 +46,12 @@ You can install this on a ComputerCraft computer using either:
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua` * `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
* `pastebin get sqUN6VUb ccmsi.lua` * `pastebin get sqUN6VUb ccmsi.lua`
## Contributing
Please reach out to me via Discord or email (or GitHub in some way) if you are thinking of making any contributions at this time. I started this project as a challenge for myself and have been enjoying having something I can work on in my own way.
Once this is out of beta I will be more open to contributions, but for now I am hoping to keep them to a minimum as the remaining challenges are ones I am looking forward to solving.
## [SCADA](https://en.wikipedia.org/wiki/SCADA) ## [SCADA](https://en.wikipedia.org/wiki/SCADA)
> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery. > Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.

View File

@@ -8,6 +8,7 @@ local network = require("scada-common.network")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local themes = require("graphics.themes")
local core = require("graphics.core") local core = require("graphics.core")
@@ -24,6 +25,8 @@ local RadioButton = require("graphics.elements.controls.radio_button")
local NumberField = require("graphics.elements.form.number_field") local NumberField = require("graphics.elements.form.number_field")
local TextField = require("graphics.elements.form.text_field") local TextField = require("graphics.elements.form.text_field")
local IndLight = require("graphics.elements.indicators.light")
local println = util.println local println = util.println
local tri = util.trinary local tri = util.trinary
@@ -39,7 +42,11 @@ local CENTER = core.ALIGN.CENTER
local RIGHT = core.ALIGN.RIGHT local RIGHT = core.ALIGN.RIGHT
-- changes to the config data/format to let the user know -- changes to the config data/format to let the user know
local changes = {} local changes = {
{ "v1.2.4", { "Added temperature scale options" } },
{ "v1.2.12", { "Added main UI theme", "Added front panel UI theme", "Added color accessibility modes" } },
{ "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
}
---@class crd_configurator ---@class crd_configurator
local configurator = {} local configurator = {}
@@ -49,21 +56,7 @@ local style = {}
style.root = cpair(colors.black, colors.lightGray) style.root = cpair(colors.black, colors.lightGray)
style.header = cpair(colors.white, colors.gray) style.header = cpair(colors.white, colors.gray)
style.colors = { style.colors = themes.smooth_stone.colors
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.lime, hex = 0x80ff80 },
{ c = colors.green, hex = 0x4aee8a },
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee },
{ c = colors.pink, hex = 0xf26ba2 },
{ c = colors.magenta, hex = 0xf9488a },
{ c = colors.lightGray, hex = 0xcacaca },
{ c = colors.gray, hex = 0x575757 }
}
local bw_fg_bg = cpair(colors.black, colors.white) local bw_fg_bg = cpair(colors.black, colors.white)
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray) local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
@@ -71,6 +64,7 @@ local nav_fg_bg = bw_fg_bg
local btn_act_fg_bg = cpair(colors.white, colors.gray) local btn_act_fg_bg = cpair(colors.white, colors.gray)
local dis_fg_bg = cpair(colors.lightGray,colors.white) local dis_fg_bg = cpair(colors.lightGray,colors.white)
---@class _crd_cfg_tool_ctl
local tool_ctl = { local tool_ctl = {
nic = nil, ---@type nic nic = nil, ---@type nic
net_listen = false, net_listen = false,
@@ -84,8 +78,12 @@ local tool_ctl = {
has_config = false, has_config = false,
viewing_config = false, viewing_config = false,
importing_legacy = false, importing_legacy = false,
jumped_to_color = false,
view_cfg = nil, ---@type graphics_element view_cfg = nil, ---@type graphics_element
color_cfg = nil, ---@type graphics_element
color_next = nil, ---@type graphics_element
color_apply = nil, ---@type graphics_element
settings_apply = nil, ---@type graphics_element settings_apply = nil, ---@type graphics_element
gen_summary = nil, ---@type function gen_summary = nil, ---@type function
@@ -119,6 +117,7 @@ local tmp_cfg = {
UnitCount = 1, UnitCount = 1,
SpeakerVolume = 1.0, SpeakerVolume = 1.0,
Time24Hour = true, Time24Hour = true,
TempScale = 1,
DisableFlowView = false, DisableFlowView = false,
MainDisplay = nil, ---@type string MainDisplay = nil, ---@type string
FlowDisplay = nil, ---@type string FlowDisplay = nil, ---@type string
@@ -133,6 +132,9 @@ local tmp_cfg = {
LogMode = 0, LogMode = 0,
LogPath = "", LogPath = "",
LogDebug = false, LogDebug = false,
MainTheme = 1,
FrontPanelTheme = 1,
ColorMode = 1
} }
---@class crd_config ---@class crd_config
@@ -148,6 +150,7 @@ local fields = {
{ "UnitDisplays", "Unit Monitors", {} }, { "UnitDisplays", "Unit Monitors", {} },
{ "SpeakerVolume", "Speaker Volume", 1.0 }, { "SpeakerVolume", "Speaker Volume", 1.0 },
{ "Time24Hour", "Use 24-hour Time Format", true }, { "Time24Hour", "Use 24-hour Time Format", true },
{ "TempScale", "Temperature Scale", 1 },
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false }, { "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
{ "SVR_Channel", "SVR Channel", 16240 }, { "SVR_Channel", "SVR Channel", 16240 },
{ "CRD_Channel", "CRD Channel", 16243 }, { "CRD_Channel", "CRD Channel", 16243 },
@@ -158,7 +161,10 @@ local fields = {
{ "AuthKey", "Facility Auth Key" , ""}, { "AuthKey", "Facility Auth Key" , ""},
{ "LogMode", "Log Mode", log.MODE.APPEND }, { "LogMode", "Log Mode", log.MODE.APPEND },
{ "LogPath", "Log Path", "/log.txt" }, { "LogPath", "Log Path", "/log.txt" },
{ "LogDebug","Log Debug Messages", false } { "LogDebug", "Log Debug Messages", false },
{ "MainTheme", "Main UI Theme", themes.UI_THEME.SMOOTH_STONE },
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
} }
-- check if a value is an integer within a range (inclusive) -- check if a value is an integer within a range (inclusive)
@@ -309,10 +315,11 @@ local function config_view(display)
local spkr_cfg = Div{parent=root_pane_div,x=1,y=1} local spkr_cfg = Div{parent=root_pane_div,x=1,y=1}
local crd_cfg = Div{parent=root_pane_div,x=1,y=1} local crd_cfg = Div{parent=root_pane_div,x=1,y=1}
local log_cfg = Div{parent=root_pane_div,x=1,y=1} local log_cfg = Div{parent=root_pane_div,x=1,y=1}
local clr_cfg = Div{parent=root_pane_div,x=1,y=1}
local summary = Div{parent=root_pane_div,x=1,y=1} local summary = Div{parent=root_pane_div,x=1,y=1}
local changelog = Div{parent=root_pane_div,x=1,y=1} local changelog = Div{parent=root_pane_div,x=1,y=1}
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,net_cfg,fac_cfg,mon_cfg,spkr_cfg,crd_cfg,log_cfg,summary,changelog}} local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,net_cfg,fac_cfg,mon_cfg,spkr_cfg,crd_cfg,log_cfg,clr_cfg,summary,changelog}}
-- Main Page -- Main Page
@@ -333,7 +340,7 @@ local function config_view(display)
tool_ctl.viewing_config = true tool_ctl.viewing_config = true
tool_ctl.gen_summary(settings_cfg) tool_ctl.gen_summary(settings_cfg)
tool_ctl.settings_apply.hide(true) tool_ctl.settings_apply.hide(true)
main_pane.set_value(8) main_pane.set_value(9)
end end
if fs.exists("/coordinator/config.lua") then if fs.exists("/coordinator/config.lua") then
@@ -344,10 +351,21 @@ local function config_view(display)
PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg} tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=dis_fg_bg}
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end local function jump_color()
tool_ctl.jumped_to_color = true
tool_ctl.color_next.hide(true)
tool_ctl.color_apply.show()
main_pane.set_value(8)
end
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(9)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
if not tool_ctl.has_config then
tool_ctl.view_cfg.disable()
tool_ctl.color_cfg.disable()
end
--#region Network --#region Network
@@ -465,7 +483,7 @@ local function config_view(display)
key_err.hide(true) key_err.hide(true)
-- init mac for supervisor connection -- init mac for supervisor connection
if string.len(v) >= 8 then network.init_mac(tmp_cfg.AuthKey) end if string.len(v) >= 8 then network.init_mac(tmp_cfg.AuthKey) else network.deinit_mac() end
main_pane.set_value(3) main_pane.set_value(3)
@@ -693,7 +711,7 @@ local function config_view(display)
mon_pane.set_value(1) mon_pane.set_value(1)
end end
PushButton{parent=mon_c_4,x=1,y=14,text="\x1b Back",callback=back_from_legacy,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=mon_c_4,x=44,y=14,min_width=6,text="Done",callback=back_from_legacy,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion --#endregion
@@ -730,8 +748,6 @@ local function config_view(display)
local crd_c_1 = Div{parent=crd_cfg,x=2,y=4,width=49} local crd_c_1 = Div{parent=crd_cfg,x=2,y=4,width=49}
local crd_pane = MultiPane{parent=crd_cfg,x=1,y=4,panes={crd_c_1}}
TextBox{parent=crd_cfg,x=1,y=2,height=1,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)} TextBox{parent=crd_cfg,x=1,y=2,height=1,text=" Coordinator UI Configuration",fg_bg=cpair(colors.black,colors.lime)}
TextBox{parent=crd_c_1,x=1,y=1,height=3,text="Configure the UI interface options below if you wish to customize formats."} TextBox{parent=crd_c_1,x=1,y=1,height=3,text="Configure the UI interface options below if you wish to customize formats."}
@@ -739,8 +755,12 @@ local function config_view(display)
TextBox{parent=crd_c_1,x=1,y=4,height=1,text="Clock Time Format"} TextBox{parent=crd_c_1,x=1,y=4,height=1,text="Clock Time Format"}
local clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} local clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
TextBox{parent=crd_c_1,x=1,y=8,height=1,text="Temperature Scale"}
local temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options={"Kelvin","Celsius","Fahrenheit","Rankine"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
local function submit_ui_opts() local function submit_ui_opts()
tmp_cfg.Time24Hour = clock_fmt.get_value() == 1 tmp_cfg.Time24Hour = clock_fmt.get_value() == 1
tmp_cfg.TempScale = temp_scale.get_value()
main_pane.set_value(7) main_pane.set_value(7)
end end
@@ -774,10 +794,8 @@ local function config_view(display)
tmp_cfg.LogMode = mode.get_value() - 1 tmp_cfg.LogMode = mode.get_value() - 1
tmp_cfg.LogPath = path.get_value() tmp_cfg.LogPath = path.get_value()
tmp_cfg.LogDebug = en_dbg.get_value() tmp_cfg.LogDebug = en_dbg.get_value()
tool_ctl.gen_summary(tmp_cfg) tool_ctl.color_apply.hide(true)
tool_ctl.viewing_config = false tool_ctl.color_next.show()
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(8) main_pane.set_value(8)
else path_err.show() end else path_err.show() end
end end
@@ -787,6 +805,121 @@ local function config_view(display)
--#endregion --#endregion
--#region Color Options
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color themes for the different UI displays."}
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Main UI Theme"}
local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_1,x=18,y=7,height=1,text="Front Panel Theme"}
local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."}
TextBox{parent=clr_c_2,x=21,y=7,height=1,text="Preview"}
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
local function recolor(value)
local c = themes.smooth_stone.color_modes[value]
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
b_off.hide()
g_off.show()
else
g_off.hide()
b_off.show()
end
if #c == 0 then
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
else
term.setPaletteColor(colors.green, c[1].hex)
term.setPaletteColor(colors.yellow, c[2].hex)
term.setPaletteColor(colors.red, c[3].hex)
end
end
TextBox{parent=clr_c_2,x=1,y=7,height=1,width=10,text="Color Mode"}
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local function back_from_colors()
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 7))
tool_ctl.jumped_to_color = false
recolor(1)
end
local function show_access()
clr_pane.set_value(2)
recolor(c_mode.get_value())
end
local function submit_colors()
tmp_cfg.MainTheme = main_theme.get_value()
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
tmp_cfg.ColorMode = c_mode.get_value()
if tool_ctl.jumped_to_color then
settings.set("MainTheme", tmp_cfg.MainTheme)
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
settings.set("ColorMode", tmp_cfg.ColorMode)
if settings.save("/coordinator.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
clr_pane.set_value(3)
else
clr_pane.set_value(4)
end
else
tool_ctl.gen_summary(tmp_cfg)
tool_ctl.viewing_config = false
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(9)
end
end
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply.hide(true)
local function c_go_home()
main_pane.set_value(1)
clr_pane.set_value(1)
end
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Summary and Saving --#region Summary and Saving
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49} local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
@@ -807,7 +940,7 @@ local function config_view(display)
tool_ctl.importing_legacy = false tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show() tool_ctl.settings_apply.show()
else else
main_pane.set_value(7) main_pane.set_value(8)
end end
end end
@@ -838,12 +971,16 @@ local function config_view(display)
try_set(mode, ini_cfg.LogMode) try_set(mode, ini_cfg.LogMode)
try_set(path, ini_cfg.LogPath) try_set(path, ini_cfg.LogPath)
try_set(en_dbg, ini_cfg.LogDebug) try_set(en_dbg, ini_cfg.LogDebug)
try_set(main_theme, ini_cfg.MainTheme)
try_set(fp_theme, ini_cfg.FrontPanelTheme)
try_set(c_mode, ini_cfg.ColorMode)
preset_monitor_fields() preset_monitor_fields()
tool_ctl.gen_mon_list() tool_ctl.gen_mon_list()
tool_ctl.view_cfg.enable() tool_ctl.view_cfg.enable()
tool_ctl.color_cfg.enable()
if tool_ctl.importing_legacy then if tool_ctl.importing_legacy then
tool_ctl.importing_legacy = false tool_ctl.importing_legacy = false
@@ -867,7 +1004,7 @@ local function config_view(display)
net_pane.set_value(1) net_pane.set_value(1)
fac_pane.set_value(1) fac_pane.set_value(1)
mon_pane.set_value(1) mon_pane.set_value(1)
crd_pane.set_value(1) clr_pane.set_value(1)
sum_pane.set_value(1) sum_pane.set_value(1)
end end
@@ -964,7 +1101,7 @@ local function config_view(display)
tool_ctl.gen_summary(tmp_cfg) tool_ctl.gen_summary(tmp_cfg)
sum_pane.set_value(1) sum_pane.set_value(1)
main_pane.set_value(8) main_pane.set_value(9)
tool_ctl.importing_legacy = true tool_ctl.importing_legacy = true
end end
@@ -1109,6 +1246,10 @@ local function config_view(display)
function tool_ctl.gen_mon_list() function tool_ctl.gen_mon_list()
mon_list.remove_all() mon_list.remove_all()
local missing = { main = tmp_cfg.MainDisplay ~= nil, flow = tmp_cfg.FlowDisplay ~= nil, unit = {} }
for i = 1, tmp_cfg.UnitCount do missing.unit[i] = tmp_cfg.UnitDisplays[i] ~= nil end
-- list connected monitors
local monitors = ppm.get_monitor_list() local monitors = ppm.get_monitor_list()
for iface, device in pairs(monitors) do for iface, device in pairs(monitors) do
local dev = device.dev local dev = device.dev
@@ -1128,11 +1269,14 @@ local function config_view(display)
if tmp_cfg.MainDisplay == iface then if tmp_cfg.MainDisplay == iface then
assignment = "Main" assignment = "Main"
missing.main = false
elseif tmp_cfg.FlowDisplay == iface then elseif tmp_cfg.FlowDisplay == iface then
assignment = "Flow" assignment = "Flow"
missing.flow = false
else else
for i = 1, tmp_cfg.UnitCount do for i = 1, tmp_cfg.UnitCount do
if tmp_cfg.UnitDisplays[i] == iface then if tmp_cfg.UnitDisplays[i] == iface then
missing.unit[i] = false
assignment = "Unit " .. i assignment = "Unit " .. i
break break
end end
@@ -1157,6 +1301,31 @@ local function config_view(display)
if assignment == "Unused" then unset.disable() end if assignment == "Unused" then unset.disable() end
end end
local dc_list = {} -- disconnected monitor list
if missing.main then table.insert(dc_list, { "Main", tmp_cfg.MainDisplay }) end
if missing.flow then table.insert(dc_list, { "Flow", tmp_cfg.FlowDisplay }) end
for i = 1, tmp_cfg.UnitCount do
if missing.unit[i] then table.insert(dc_list, { "Unit " .. i, tmp_cfg.UnitDisplays[i] }) end
end
-- add monitors that are assigned but not connected
for i = 1, #dc_list do
local line = Div{parent=mon_list,x=1,y=1,height=1}
TextBox{parent=line,x=1,y=1,width=6,height=1,text=dc_list[i][1],fg_bg=cpair(colors.blue,colors.white)}
TextBox{parent=line,x=8,y=1,height=1,text="disconnected",fg_bg=cpair(colors.red,colors.white)}
local function unset_mon()
purge_assignments(dc_list[i][2])
tool_ctl.gen_mon_list()
end
TextBox{parent=line,x=33,y=1,width=4,height=1,text="?x?",fg_bg=cpair(colors.black,colors.white)}
PushButton{parent=line,x=37,y=1,min_width=5,height=1,text="SET",callback=function()end,dis_fg_bg=cpair(colors.black,colors.gray)}.disable()
PushButton{parent=line,x=42,y=1,min_width=7,height=1,text="UNSET",callback=unset_mon,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.black,colors.gray)}
end
end end
-- expose the auth key on the summary page -- expose the auth key on the summary page
@@ -1186,6 +1355,14 @@ local function config_view(display)
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
elseif f[1] == "TempScale" then
if raw == 1 then val = "Kelvin" elseif raw == 2 then val = "Celsius" elseif raw == 3 then val = "Fahrenheit" elseif raw == 4 then val = "Rankine" end
elseif f[1] == "MainTheme" then
val = util.strval(themes.ui_theme_name(raw))
elseif f[1] == "FrontPanelTheme" then
val = util.strval(themes.fp_theme_name(raw))
elseif f[1] == "ColorMode" then
val = util.strval(themes.color_mode_name(raw))
elseif f[1] == "UnitDisplays" and type(cfg.UnitDisplays) == "table" then elseif f[1] == "UnitDisplays" and type(cfg.UnitDisplays) == "table" then
val = "" val = ""
for idx = 1, #cfg.UnitDisplays do for idx = 1, #cfg.UnitDisplays do
@@ -1272,9 +1449,11 @@ function configurator.configure(start_code, message)
elseif event == "paste" then elseif event == "paste" then
display.handle_paste(param1) display.handle_paste(param1)
elseif event == "peripheral_detach" then elseif event == "peripheral_detach" then
---@diagnostic disable-next-line: discard-returns
ppm.handle_unmount(param1) ppm.handle_unmount(param1)
tool_ctl.gen_mon_list() tool_ctl.gen_mon_list()
elseif event == "peripheral" then elseif event == "peripheral" then
---@diagnostic disable-next-line: discard-returns
ppm.mount(param1) ppm.mount(param1)
tool_ctl.gen_mon_list() tool_ctl.gen_mon_list()
elseif event == "monitor_resize" then elseif event == "monitor_resize" then

View File

@@ -4,6 +4,8 @@ local ppm = require("scada-common.ppm")
local util = require("scada-common.util") local util = require("scada-common.util")
local types = require("scada-common.types") local types = require("scada-common.types")
local themes = require("graphics.themes")
local iocontrol = require("coordinator.iocontrol") local iocontrol = require("coordinator.iocontrol")
local process = require("coordinator.process") local process = require("coordinator.process")
@@ -35,6 +37,7 @@ function coordinator.load_config()
config.UnitCount = settings.get("UnitCount") config.UnitCount = settings.get("UnitCount")
config.SpeakerVolume = settings.get("SpeakerVolume") config.SpeakerVolume = settings.get("SpeakerVolume")
config.Time24Hour = settings.get("Time24Hour") config.Time24Hour = settings.get("Time24Hour")
config.TempScale = settings.get("TempScale")
config.DisableFlowView = settings.get("DisableFlowView") config.DisableFlowView = settings.get("DisableFlowView")
config.MainDisplay = settings.get("MainDisplay") config.MainDisplay = settings.get("MainDisplay")
@@ -53,11 +56,17 @@ function coordinator.load_config()
config.LogPath = settings.get("LogPath") config.LogPath = settings.get("LogPath")
config.LogDebug = settings.get("LogDebug") config.LogDebug = settings.get("LogDebug")
config.MainTheme = settings.get("MainTheme")
config.FrontPanelTheme = settings.get("FrontPanelTheme")
config.ColorMode = settings.get("ColorMode")
local cfv = util.new_validator() local cfv = util.new_validator()
cfv.assert_type_int(config.UnitCount) cfv.assert_type_int(config.UnitCount)
cfv.assert_range(config.UnitCount, 1, 4) cfv.assert_range(config.UnitCount, 1, 4)
cfv.assert_type_bool(config.Time24Hour) cfv.assert_type_bool(config.Time24Hour)
cfv.assert_type_int(config.TempScale)
cfv.assert_range(config.TempScale, 1, 4)
cfv.assert_type_bool(config.DisableFlowView) cfv.assert_type_bool(config.DisableFlowView)
cfv.assert_type_table(config.UnitDisplays) cfv.assert_type_table(config.UnitDisplays)
@@ -80,7 +89,7 @@ function coordinator.load_config()
if type(config.AuthKey) == "string" then if type(config.AuthKey) == "string" then
local len = string.len(config.AuthKey) local len = string.len(config.AuthKey)
cfv.assert_eq(len == 0 or len >= 8, true) cfv.assert(len == 0 or len >= 8)
end end
cfv.assert_type_int(config.LogMode) cfv.assert_type_int(config.LogMode)
@@ -88,13 +97,20 @@ function coordinator.load_config()
cfv.assert_type_str(config.LogPath) cfv.assert_type_str(config.LogPath)
cfv.assert_type_bool(config.LogDebug) cfv.assert_type_bool(config.LogDebug)
cfv.assert_type_int(config.MainTheme)
cfv.assert_range(config.MainTheme, 1, 2)
cfv.assert_type_int(config.FrontPanelTheme)
cfv.assert_range(config.FrontPanelTheme, 1, 2)
cfv.assert_type_int(config.ColorMode)
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
-- Monitor Setup -- Monitor Setup
---@class monitors_struct ---@class monitors_struct
local monitors = { local monitors = {
primary = nil, ---@type table|nil main = nil, ---@type table|nil
primary_name = "", main_name = "",
flow = nil, ---@type table|nil flow = nil, ---@type table|nil
flow_name = "", flow_name = "",
unit_displays = {}, unit_displays = {},
unit_name_map = {} unit_name_map = {}
@@ -118,11 +134,11 @@ function coordinator.load_config()
return 2, "Main monitor is not connected." return 2, "Main monitor is not connected."
end end
monitors.primary = ppm.get_periph(config.MainDisplay) monitors.main = ppm.get_periph(config.MainDisplay)
monitors.primary_name = config.MainDisplay monitors.main_name = config.MainDisplay
monitors.primary.setTextScale(0.5) monitors.main.setTextScale(0.5)
w, _ = ppm.monitor_block_size(monitors.primary.getSize()) w, _ = ppm.monitor_block_size(monitors.main.getSize())
if w ~= 8 then if w ~= 8 then
return 2, util.c("Main monitor width is incorrect (was ", w, ", must be 8).") return 2, util.c("Main monitor width is incorrect (was ", w, ", must be 8).")
end end
@@ -176,7 +192,7 @@ end
---@return function? update, function? done ---@return function? update, function? done
local function log_dmesg(message, dmesg_tag, working) local function log_dmesg(message, dmesg_tag, working)
local colors = { local colors = {
GRAPHICS = colors.green, RENDER = colors.green,
SYSTEM = colors.cyan, SYSTEM = colors.cyan,
BOOT = colors.blue, BOOT = colors.blue,
COMMS = colors.purple, COMMS = colors.purple,
@@ -190,7 +206,7 @@ local function log_dmesg(message, dmesg_tag, working)
end end
end end
function coordinator.log_graphics(message) log_dmesg(message, "GRAPHICS") end function coordinator.log_render(message) log_dmesg(message, "RENDER") end
function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end
function coordinator.log_boot(message) log_dmesg(message, "BOOT") end function coordinator.log_boot(message) log_dmesg(message, "BOOT") end
function coordinator.log_comms(message) log_dmesg(message, "COMMS") end function coordinator.log_comms(message) log_dmesg(message, "COMMS") end
@@ -263,11 +279,12 @@ function coordinator.comms(version, nic, sv_watchdog)
-- send an API establish request response -- send an API establish request response
---@param packet scada_packet ---@param packet scada_packet
---@param ack ESTABLISH_ACK ---@param ack ESTABLISH_ACK
local function _send_api_establish_ack(packet, ack) ---@param data any?
local function _send_api_establish_ack(packet, ack, data)
local s_pkt = comms.scada_packet() local s_pkt = comms.scada_packet()
local m_pkt = comms.mgmt_packet() local m_pkt = comms.mgmt_packet()
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack }) m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data })
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt) nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
@@ -299,7 +316,7 @@ function coordinator.comms(version, nic, sv_watchdog)
if not self.sv_linked then if not self.sv_linked then
if self.est_tick_waiting == nil then if self.est_tick_waiting == nil then
self.est_start = util.time_s() self.est_start = os.clock()
self.est_last = self.est_start self.est_last = self.est_start
self.est_tick_waiting, self.est_task_done = self.est_tick_waiting, self.est_task_done =
@@ -307,10 +324,10 @@ function coordinator.comms(version, nic, sv_watchdog)
_send_establish() _send_establish()
else else
self.est_tick_waiting(math.max(0, LINK_TIMEOUT - (util.time_s() - self.est_start))) self.est_tick_waiting(math.max(0, LINK_TIMEOUT - (os.clock() - self.est_start)))
end end
if abort or (util.time_s() - self.est_start) >= LINK_TIMEOUT then if abort or (os.clock() - self.est_start) >= LINK_TIMEOUT then
self.est_task_done(false) self.est_task_done(false)
if abort then if abort then
@@ -331,11 +348,12 @@ function coordinator.comms(version, nic, sv_watchdog)
ok = false ok = false
elseif self.sv_config_err then elseif self.sv_config_err then
self.est_task_done(false)
coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs") coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs")
ok = false ok = false
elseif (util.time_s() - self.est_last) > 1.0 then elseif (os.clock() - self.est_last) > 1.0 then
_send_establish() _send_establish()
self.est_last = util.time_s() self.est_last = os.clock()
end end
elseif self.est_tick_waiting ~= nil then elseif self.est_tick_waiting ~= nil then
self.est_task_done(true) self.est_task_done(true)
@@ -454,10 +472,11 @@ function coordinator.comms(version, nic, sv_watchdog)
elseif packet.type == MGMT_TYPE.ESTABLISH then elseif packet.type == MGMT_TYPE.ESTABLISH then
-- establish a new session -- establish a new session
-- validate packet and continue -- validate packet and continue
if packet.length == 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then if packet.length == 4 then
local comms_v = packet.data[1] local comms_v = util.strval(packet.data[1])
local firmware_v = packet.data[2] local firmware_v = util.strval(packet.data[2])
local dev_type = packet.data[3] local dev_type = packet.data[3]
local api_v = util.strval(packet.data[4])
if comms_v ~= comms.version then if comms_v ~= comms.version then
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
@@ -465,12 +484,19 @@ function coordinator.comms(version, nic, sv_watchdog)
end end
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
elseif api_v ~= comms.api_version then
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_API_VERSION then
log.info(util.c("dropping API establish packet with incorrect api version v", api_v, " (expected v", comms.api_version, ")"))
end
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION)
elseif dev_type == DEVICE_TYPE.PKT then elseif dev_type == DEVICE_TYPE.PKT then
-- pocket linking request -- pocket linking request
local id = apisessions.establish_session(src_addr, firmware_v) local id = apisessions.establish_session(src_addr, firmware_v)
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id)) coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW) local conf = iocontrol.get_db().facility.conf
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW, { conf.num_units, conf.cooling })
else else
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel")) log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY) _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
@@ -676,7 +702,7 @@ function coordinator.comms(version, nic, sv_watchdog)
if conf.num_units == config.UnitCount then if conf.num_units == config.UnitCount then
-- init io controller -- init io controller
iocontrol.init(conf, public) iocontrol.init(conf, public, config.TempScale)
self.sv_addr = src_addr self.sv_addr = src_addr
self.sv_linked = true self.sv_linked = true

View File

@@ -47,10 +47,27 @@ end
-- initialize the coordinator IO controller -- initialize the coordinator IO controller
---@param conf facility_conf configuration ---@param conf facility_conf configuration
---@param comms coord_comms comms reference ---@param comms coord_comms comms reference
function iocontrol.init(conf, comms) ---@param temp_scale integer temperature unit (1 = K, 2 = C, 3 = F, 4 = R)
function iocontrol.init(conf, comms, temp_scale)
-- temperature unit label and conversion function (from Kelvin)
if temp_scale == 2 then
io.temp_label = "\xb0C"
io.temp_convert = function (t) return t - 273.15 end
elseif temp_scale == 3 then
io.temp_label = "\xb0F"
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
elseif temp_scale == 4 then
io.temp_label = "\xb0R"
io.temp_convert = function (t) return 1.8 * t end
else
io.temp_label = "K"
io.temp_convert = function (t) return t end
end
-- facility data structure -- facility data structure
---@class ioctl_facility ---@class ioctl_facility
io.facility = { io.facility = {
conf = conf,
num_units = conf.num_units, num_units = conf.num_units,
tank_mode = conf.cooling.fac_tank_mode, tank_mode = conf.cooling.fac_tank_mode,
tank_defs = conf.cooling.fac_tank_defs, tank_defs = conf.cooling.fac_tank_defs,
@@ -75,6 +92,7 @@ function iocontrol.init(conf, comms)
---@type WASTE_PRODUCT ---@type WASTE_PRODUCT
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
auto_pu_fallback_active = false, auto_pu_fallback_active = false,
auto_sps_disabled = false,
radiation = types.new_zero_radiation_reading(), radiation = types.new_zero_radiation_reading(),
@@ -210,6 +228,8 @@ function iocontrol.init(conf, comms)
---@class ioctl_unit ---@class ioctl_unit
local entry = { local entry = {
unit_id = i, unit_id = i,
connected = false,
rtu_hw = { boilers = {}, turbines = {} },
num_boilers = 0, num_boilers = 0,
num_turbines = 0, num_turbines = 0,
@@ -219,7 +239,10 @@ function iocontrol.init(conf, comms)
control_state = false, control_state = false,
burn_rate_cmd = 0.0, burn_rate_cmd = 0.0,
radiation = types.new_zero_radiation_reading(), radiation = types.new_zero_radiation_reading(),
sna_prod_rate = 0.0,
sna_peak_rate = 0.0,
sna_max_rate = 0.0,
sna_out_rate = 0.0,
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
waste_product = types.WASTE_PRODUCT.PLUTONIUM, waste_product = types.WASTE_PRODUCT.PLUTONIUM,
@@ -260,18 +283,18 @@ function iocontrol.init(conf, comms)
---@type alarms ---@type alarms
alarms = { alarms = {
ALARM_STATE.INACTIVE, -- containment breach ALARM_STATE.INACTIVE, -- containment breach
ALARM_STATE.INACTIVE, -- containment radiation ALARM_STATE.INACTIVE, -- containment radiation
ALARM_STATE.INACTIVE, -- reactor lost ALARM_STATE.INACTIVE, -- reactor lost
ALARM_STATE.INACTIVE, -- damage critical ALARM_STATE.INACTIVE, -- damage critical
ALARM_STATE.INACTIVE, -- reactor taking damage ALARM_STATE.INACTIVE, -- reactor taking damage
ALARM_STATE.INACTIVE, -- reactor over temperature ALARM_STATE.INACTIVE, -- reactor over temperature
ALARM_STATE.INACTIVE, -- reactor high temperature ALARM_STATE.INACTIVE, -- reactor high temperature
ALARM_STATE.INACTIVE, -- waste leak ALARM_STATE.INACTIVE, -- waste leak
ALARM_STATE.INACTIVE, -- waste level high ALARM_STATE.INACTIVE, -- waste level high
ALARM_STATE.INACTIVE, -- RPS transient ALARM_STATE.INACTIVE, -- RPS transient
ALARM_STATE.INACTIVE, -- RCS transient ALARM_STATE.INACTIVE, -- RCS transient
ALARM_STATE.INACTIVE -- turbine trip ALARM_STATE.INACTIVE -- turbine trip
}, },
annunciator = {}, ---@type annunciator annunciator = {}, ---@type annunciator
@@ -298,12 +321,14 @@ function iocontrol.init(conf, comms)
for _ = 1, conf.cooling.r_cool[i].BoilerCount do for _ = 1, conf.cooling.r_cool[i].BoilerCount do
table.insert(entry.boiler_ps_tbl, psil.create()) table.insert(entry.boiler_ps_tbl, psil.create())
table.insert(entry.boiler_data_tbl, {}) table.insert(entry.boiler_data_tbl, {})
table.insert(entry.rtu_hw.boilers, { connected = false, faulted = false })
end end
-- create turbine tables -- create turbine tables
for _ = 1, conf.cooling.r_cool[i].TurbineCount do for _ = 1, conf.cooling.r_cool[i].TurbineCount do
table.insert(entry.turbine_ps_tbl, psil.create()) table.insert(entry.turbine_ps_tbl, psil.create())
table.insert(entry.turbine_data_tbl, {}) table.insert(entry.turbine_data_tbl, {})
table.insert(entry.rtu_hw.turbines, { connected = false, faulted = false })
end end
-- create tank tables -- create tank tables
@@ -357,6 +382,13 @@ function iocontrol.fp_monitor_state(id, connected)
end end
end end
-- report thread (routine) statuses
---@param thread string thread name
---@param ok boolean thread state
function iocontrol.fp_rt_status(thread, ok)
io.fp.ps.publish(util.c("routine__", thread), ok)
end
-- report PKT firmware version and PKT session connection state -- report PKT firmware version and PKT session connection state
---@param session_id integer PKT session ---@param session_id integer PKT session
---@param fw string firmware version ---@param fw string firmware version
@@ -384,7 +416,7 @@ function iocontrol.fp_pkt_rtt(session_id, rtt)
elseif rtt > WARN_RTT then elseif rtt > WARN_RTT then
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.yellow_hc) io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.yellow_hc)
else else
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green) io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green_hc)
end end
end end
@@ -566,7 +598,7 @@ function iocontrol.update_facility_status(status)
local ctl_status = status[1] local ctl_status = status[1]
if type(ctl_status) == "table" and #ctl_status == 16 then if type(ctl_status) == "table" and #ctl_status == 17 then
fac.all_sys_ok = ctl_status[1] fac.all_sys_ok = ctl_status[1]
fac.auto_ready = ctl_status[2] fac.auto_ready = ctl_status[2]
@@ -617,9 +649,11 @@ function iocontrol.update_facility_status(status)
fac.auto_current_waste_product = ctl_status[15] fac.auto_current_waste_product = ctl_status[15]
fac.auto_pu_fallback_active = ctl_status[16] fac.auto_pu_fallback_active = ctl_status[16]
fac.auto_sps_disabled = ctl_status[17]
fac.ps.publish("current_waste_product", fac.auto_current_waste_product) fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active) fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled)
else else
log.debug(log_header .. "control status not a table or length mismatch") log.debug(log_header .. "control status not a table or length mismatch")
valid = false valid = false
@@ -636,10 +670,27 @@ function iocontrol.update_facility_status(status)
fac.rtu_count = rtu_statuses.count fac.rtu_count = rtu_statuses.count
-- power statistics -- power statistics
if type(rtu_statuses.power) == "table" then if type(rtu_statuses.power) == "table" and #rtu_statuses.power == 4 then
fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1]) local data = fac.induction_data_tbl[1] ---@type imatrix_session_db
fac.induction_ps_tbl[1].publish("avg_inflow", rtu_statuses.power[2]) local ps = fac.induction_ps_tbl[1] ---@type psil
fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3])
local chg = tonumber(rtu_statuses.power[1])
local in_f = tonumber(rtu_statuses.power[2])
local out_f = tonumber(rtu_statuses.power[3])
local eta = tonumber(rtu_statuses.power[4])
ps.publish("avg_charge", chg)
ps.publish("avg_inflow", in_f)
ps.publish("avg_outflow", out_f)
ps.publish("eta_ms", eta)
ps.publish("is_charging", in_f > out_f)
ps.publish("is_discharging", out_f > in_f)
if data and data.build then
local cap = util.joules_to_fe(data.build.transfer_cap)
ps.publish("at_max_io", in_f >= cap or out_f >= cap)
end
else else
log.debug(log_header .. "power statistics list not a table") log.debug(log_header .. "power statistics list not a table")
valid = false valid = false
@@ -850,6 +901,7 @@ function iocontrol.update_unit_statuses(statuses)
end end
if #reactor_status == 0 then if #reactor_status == 0 then
unit.connected = false
unit.unit_ps.publish("computed_status", 1) -- disconnected unit.unit_ps.publish("computed_status", 1) -- disconnected
elseif #reactor_status == 3 then elseif #reactor_status == 3 then
local mek_status = reactor_status[1] local mek_status = reactor_status[1]
@@ -909,6 +961,8 @@ function iocontrol.update_unit_statuses(statuses)
unit.unit_ps.publish(key, val) unit.unit_ps.publish(key, val)
end end
end end
unit.connected = true
else else
log.debug(log_header .. "reactor status length mismatch") log.debug(log_header .. "reactor status length mismatch")
valid = false valid = false
@@ -923,7 +977,10 @@ function iocontrol.update_unit_statuses(statuses)
local boil_sum = 0 local boil_sum = 0
for id = 1, #unit.boiler_ps_tbl do for id = 1, #unit.boiler_ps_tbl do
if rtu_statuses.boilers[i] == nil then local connected = rtu_statuses.boilers[id] ~= nil
unit.rtu_hw.boilers[id].connected = connected
if not connected then
-- disconnected -- disconnected
unit.boiler_ps_tbl[id].publish("computed_status", 1) unit.boiler_ps_tbl[id].publish("computed_status", 1)
end end
@@ -935,6 +992,7 @@ function iocontrol.update_unit_statuses(statuses)
local ps = unit.boiler_ps_tbl[id] ---@type psil local ps = unit.boiler_ps_tbl[id] ---@type psil
local rtu_faulted = _record_multiblock_status(boiler, data, ps) local rtu_faulted = _record_multiblock_status(boiler, data, ps)
unit.rtu_hw.boilers[id].faulted = rtu_faulted
if rtu_faulted then if rtu_faulted then
ps.publish("computed_status", 3) -- faulted ps.publish("computed_status", 3) -- faulted
@@ -966,7 +1024,10 @@ function iocontrol.update_unit_statuses(statuses)
local flow_sum = 0 local flow_sum = 0
for id = 1, #unit.turbine_ps_tbl do for id = 1, #unit.turbine_ps_tbl do
if rtu_statuses.turbines[i] == nil then local connected = rtu_statuses.turbines[id] ~= nil
unit.rtu_hw.turbines[id].connected = connected
if not connected then
-- disconnected -- disconnected
unit.turbine_ps_tbl[id].publish("computed_status", 1) unit.turbine_ps_tbl[id].publish("computed_status", 1)
end end
@@ -978,6 +1039,7 @@ function iocontrol.update_unit_statuses(statuses)
local ps = unit.turbine_ps_tbl[id] ---@type psil local ps = unit.turbine_ps_tbl[id] ---@type psil
local rtu_faulted = _record_multiblock_status(turbine, data, ps) local rtu_faulted = _record_multiblock_status(turbine, data, ps)
unit.rtu_hw.turbines[id].faulted = rtu_faulted
if rtu_faulted then if rtu_faulted then
ps.publish("computed_status", 3) -- faulted ps.publish("computed_status", 3) -- faulted
@@ -1009,7 +1071,7 @@ function iocontrol.update_unit_statuses(statuses)
-- dynamic tank statuses -- dynamic tank statuses
if type(rtu_statuses.tanks) == "table" then if type(rtu_statuses.tanks) == "table" then
for id = 1, #unit.tank_ps_tbl do for id = 1, #unit.tank_ps_tbl do
if rtu_statuses.tanks[i] == nil then if rtu_statuses.tanks[id] == nil then
-- disconnected -- disconnected
unit.tank_ps_tbl[id].publish("computed_status", 1) unit.tank_ps_tbl[id].publish("computed_status", 1)
end end
@@ -1048,12 +1110,14 @@ function iocontrol.update_unit_statuses(statuses)
-- solar neutron activator status info -- solar neutron activator status info
if type(rtu_statuses.sna) == "table" then if type(rtu_statuses.sna) == "table" then
unit.num_snas = rtu_statuses.sna[1] ---@type integer unit.num_snas = rtu_statuses.sna[1] ---@type integer
unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number unit.sna_peak_rate = rtu_statuses.sna[2] ---@type number
unit.sna_peak_rate = rtu_statuses.sna[3] ---@type number unit.sna_max_rate = rtu_statuses.sna[3] ---@type number
unit.sna_out_rate = rtu_statuses.sna[4] ---@type number
unit.unit_ps.publish("sna_count", unit.num_snas) unit.unit_ps.publish("sna_count", unit.num_snas)
unit.unit_ps.publish("sna_prod_rate", unit.sna_prod_rate)
unit.unit_ps.publish("sna_peak_rate", unit.sna_peak_rate) unit.unit_ps.publish("sna_peak_rate", unit.sna_peak_rate)
unit.unit_ps.publish("sna_max_rate", unit.sna_max_rate)
unit.unit_ps.publish("sna_out_rate", unit.sna_out_rate)
sna_count_sum = sna_count_sum + unit.num_snas sna_count_sum = sna_count_sum + unit.num_snas
else else
@@ -1201,7 +1265,7 @@ function iocontrol.update_unit_statuses(statuses)
local u_spent_rate = waste_rate local u_spent_rate = waste_rate
local u_pu_rate = util.trinary(is_pu, waste_rate, 0.0) local u_pu_rate = util.trinary(is_pu, waste_rate, 0.0)
local u_po_rate = util.trinary(not is_pu, math.min(waste_rate, unit.sna_prod_rate), 0.0) local u_po_rate = unit.sna_out_rate
unit.unit_ps.publish("pu_rate", u_pu_rate) unit.unit_ps.publish("pu_rate", u_pu_rate)
unit.unit_ps.publish("po_rate", u_po_rate) unit.unit_ps.publish("po_rate", u_po_rate)
@@ -1209,14 +1273,15 @@ function iocontrol.update_unit_statuses(statuses)
unit.unit_ps.publish("sna_in", util.trinary(is_pu, 0, burn_rate)) unit.unit_ps.publish("sna_in", util.trinary(is_pu, 0, burn_rate))
if unit.waste_product == types.WASTE_PRODUCT.POLONIUM then if unit.waste_product == types.WASTE_PRODUCT.POLONIUM then
u_spent_rate = u_po_rate
unit.unit_ps.publish("po_pl_rate", u_po_rate) unit.unit_ps.publish("po_pl_rate", u_po_rate)
unit.unit_ps.publish("po_am_rate", 0) unit.unit_ps.publish("po_am_rate", 0)
po_pl_rate = po_pl_rate + u_po_rate po_pl_rate = po_pl_rate + u_po_rate
elseif unit.waste_product == types.WASTE_PRODUCT.ANTI_MATTER then elseif unit.waste_product == types.WASTE_PRODUCT.ANTI_MATTER then
u_spent_rate = 0
unit.unit_ps.publish("po_pl_rate", 0) unit.unit_ps.publish("po_pl_rate", 0)
unit.unit_ps.publish("po_am_rate", u_po_rate) unit.unit_ps.publish("po_am_rate", u_po_rate)
po_am_rate = po_am_rate + u_po_rate po_am_rate = po_am_rate + u_po_rate
u_spent_rate = 0
else else
unit.unit_ps.publish("po_pl_rate", 0) unit.unit_ps.publish("po_pl_rate", 0)
unit.unit_ps.publish("po_am_rate", 0) unit.unit_ps.publish("po_am_rate", 0)

View File

@@ -29,7 +29,8 @@ local self = {
gen_target = 0.0, gen_target = 0.0,
limits = {}, limits = {},
waste_product = PRODUCT.PLUTONIUM, waste_product = PRODUCT.PLUTONIUM,
pu_fallback = false pu_fallback = false,
sps_low_power = false
}, },
waste_modes = {}, waste_modes = {},
priority_groups = {} priority_groups = {}
@@ -65,6 +66,7 @@ function process.init(iocontrol, coord_comms)
ctl_proc.limits = config.limits ctl_proc.limits = config.limits
ctl_proc.waste_product = config.waste_product ctl_proc.waste_product = config.waste_product
ctl_proc.pu_fallback = config.pu_fallback ctl_proc.pu_fallback = config.pu_fallback
ctl_proc.sps_low_power = config.sps_low_power
self.io.facility.ps.publish("process_mode", ctl_proc.mode) self.io.facility.ps.publish("process_mode", ctl_proc.mode)
self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target)
@@ -72,6 +74,7 @@ function process.init(iocontrol, coord_comms)
self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target) self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target)
self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product) self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product)
self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback) self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback)
self.io.facility.ps.publish("process_sps_low_power", ctl_proc.sps_low_power)
for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do
local unit = self.io.units[id] ---@type ioctl_unit local unit = self.io.units[id] ---@type ioctl_unit
@@ -83,6 +86,7 @@ function process.init(iocontrol, coord_comms)
-- notify supervisor of auto waste config -- notify supervisor of auto waste config
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product) self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product)
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback) self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback)
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, ctl_proc.sps_low_power)
end end
-- unit waste states -- unit waste states
@@ -259,6 +263,18 @@ function process.set_pu_fallback(enabled)
_write_auto_config() _write_auto_config()
end end
-- set automatic process control SPS usage at low power
---@param enabled boolean whether to enable SPS usage at low power
function process.set_sps_low_power(enabled)
self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, enabled)
log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled))
-- update config table and save
self.control_states.process.sps_low_power = enabled
_write_auto_config()
end
-- save process control settings -- save process control settings
---@param mode PROCESS control mode ---@param mode PROCESS control mode
---@param burn_target number burn rate target ---@param burn_target number burn rate target

View File

@@ -2,28 +2,33 @@
-- Graphics Rendering Control -- Graphics Rendering Control
-- --
local log = require("scada-common.log") local log = require("scada-common.log")
local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol") local coordinator = require("coordinator.coordinator")
local iocontrol = require("coordinator.iocontrol")
local style = require("coordinator.ui.style") local style = require("coordinator.ui.style")
local pgi = require("coordinator.ui.pgi") local pgi = require("coordinator.ui.pgi")
local flow_view = require("coordinator.ui.layout.flow_view") local flow_view = require("coordinator.ui.layout.flow_view")
local panel_view = require("coordinator.ui.layout.front_panel") local panel_view = require("coordinator.ui.layout.front_panel")
local main_view = require("coordinator.ui.layout.main_view") local main_view = require("coordinator.ui.layout.main_view")
local unit_view = require("coordinator.ui.layout.unit_view") local unit_view = require("coordinator.ui.layout.unit_view")
local core = require("graphics.core") local core = require("graphics.core")
local flasher = require("graphics.flasher") local flasher = require("graphics.flasher")
local DisplayBox = require("graphics.elements.displaybox") local DisplayBox = require("graphics.elements.displaybox")
local log_render = coordinator.log_render
---@class coord_renderer ---@class coord_renderer
local renderer = {} local renderer = {}
-- render engine -- render engine
local engine = { local engine = {
color_mode = 1, ---@type COLOR_MODE
monitors = nil, ---@type monitors_struct|nil monitors = nil, ---@type monitors_struct|nil
dmesg_window = nil, ---@type table|nil dmesg_window = nil, ---@type table|nil
ui_ready = false, ui_ready = false,
@@ -47,15 +52,34 @@ local function _init_display(monitor)
monitor.setCursorPos(1, 1) monitor.setCursorPos(1, 1)
-- set overridden colors -- set overridden colors
for i = 1, #style.colors do for i = 1, #style.theme.colors do
monitor.setPaletteColor(style.colors[i].c, style.colors[i].hex) monitor.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
end
-- apply color mode
local c_mode_overrides = style.theme.color_modes[engine.color_mode]
for i = 1, #c_mode_overrides do
monitor.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
end end
end end
-- disable the flow view -- print out that the monitor is too small
---@param disable boolean ---@param monitor table monitor
function renderer.legacy_disable_flow_view(disable) local function _print_too_small(monitor)
engine.disable_flow_view = disable monitor.setCursorPos(1, 1)
monitor.setBackgroundColor(colors.black)
monitor.setTextColor(colors.red)
monitor.clear()
monitor.write("monitor too small")
end
-- apply renderer configurations
---@param config crd_config
function renderer.configure(config)
style.set_themes(config.MainTheme, config.FrontPanelTheme, config.ColorMode)
engine.color_mode = config.ColorMode
engine.disable_flow_view = config.DisableFlowView
end end
-- link to the monitor peripherals -- link to the monitor peripherals
@@ -64,15 +88,15 @@ function renderer.set_displays(monitors)
engine.monitors = monitors engine.monitors = monitors
-- report to front panel as connected -- report to front panel as connected
iocontrol.fp_monitor_state("main", engine.monitors.primary ~= nil) iocontrol.fp_monitor_state("main", engine.monitors.main ~= nil)
iocontrol.fp_monitor_state("flow", engine.monitors.flow ~= nil) iocontrol.fp_monitor_state("flow", engine.monitors.flow ~= nil)
for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end
end end
-- init all displays in use by the renderer -- init all displays in use by the renderer
function renderer.init_displays() function renderer.init_displays()
-- init primary and flow monitors -- init main and flow monitors
_init_display(engine.monitors.primary) _init_display(engine.monitors.main)
if not engine.disable_flow_view then _init_display(engine.monitors.flow) end if not engine.disable_flow_view then _init_display(engine.monitors.flow) end
-- init unit displays -- init unit displays
@@ -87,15 +111,21 @@ function renderer.init_displays()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
-- set overridden colors -- set overridden colors
for i = 1, #style.fp.colors do for i = 1, #style.fp_theme.colors do
term.setPaletteColor(style.fp.colors[i].c, style.fp.colors[i].hex) term.setPaletteColor(style.fp_theme.colors[i].c, style.fp_theme.colors[i].hex)
end
-- apply color mode
local c_mode_overrides = style.fp_theme.color_modes[engine.color_mode]
for i = 1, #c_mode_overrides do
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
end end
end end
-- initialize the dmesg output window -- initialize the dmesg output window
function renderer.init_dmesg() function renderer.init_dmesg()
local disp_x, disp_y = engine.monitors.primary.getSize() local disp_w, disp_h = engine.monitors.main.getSize()
engine.dmesg_window = window.create(engine.monitors.primary, 1, 1, disp_x, disp_y) engine.dmesg_window = window.create(engine.monitors.main, 1, 1, disp_w, disp_h)
log.direct_dmesg(engine.dmesg_window) log.direct_dmesg(engine.dmesg_window)
end end
@@ -142,9 +172,9 @@ function renderer.close_fp()
engine.fp_ready = false engine.fp_ready = false
-- restore colors -- restore colors
for i = 1, #style.colors do for i = 1, #style.fp_theme.colors do
local r, g, b = term.nativePaletteColor(style.colors[i].c) local r, g, b = term.nativePaletteColor(style.fp_theme.colors[i].c)
term.setPaletteColor(style.colors[i].c, r, g, b) term.setPaletteColor(style.fp_theme.colors[i].c, r, g, b)
end end
-- reset terminal -- reset terminal
@@ -166,21 +196,24 @@ function renderer.try_start_ui()
status, msg = pcall(function () status, msg = pcall(function ()
-- show main view on main monitor -- show main view on main monitor
if engine.monitors.primary ~= nil then if engine.monitors.main ~= nil then
engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root} engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root}
main_view(engine.ui.main_display) main_view(engine.ui.main_display)
util.nop()
end end
-- show flow view on flow monitor -- show flow view on flow monitor
if engine.monitors.flow ~= nil then if engine.monitors.flow ~= nil then
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root} engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
flow_view(engine.ui.flow_display) flow_view(engine.ui.flow_display)
util.nop()
end end
-- show unit views on unit displays -- show unit views on unit displays
for idx, display in pairs(engine.monitors.unit_displays) do for idx, display in pairs(engine.monitors.unit_displays) do
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root} engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
unit_view(engine.ui.unit_displays[idx], idx) unit_view(engine.ui.unit_displays[idx], idx)
util.nop()
end end
end) end)
@@ -221,6 +254,11 @@ function renderer.close_ui()
-- clear unit monitors -- clear unit monitors
for _, monitor in ipairs(engine.monitors.unit_displays) do monitor.clear() end for _, monitor in ipairs(engine.monitors.unit_displays) do monitor.clear() end
if not engine.disable_flow_view then
-- clear flow monitor
engine.monitors.flow.clear()
end
-- re-draw dmesg -- re-draw dmesg
engine.dmesg_window.setVisible(true) engine.dmesg_window.setVisible(true)
engine.dmesg_window.redraw() engine.dmesg_window.redraw()
@@ -242,43 +280,43 @@ function renderer.ui_ready() return engine.ui_ready end
function renderer.handle_disconnect(device) function renderer.handle_disconnect(device)
local is_used = false local is_used = false
if engine.monitors ~= nil then if not engine.monitors then return false end
if engine.monitors.primary == device then
if engine.ui.main_display ~= nil then
-- delete element tree and clear root UI elements
engine.ui.main_display.delete()
end
is_used = true if engine.monitors.main == device then
engine.monitors.primary = nil if engine.ui.main_display ~= nil then
engine.ui.main_display = nil -- delete element tree and clear root UI elements
engine.ui.main_display.delete()
end
iocontrol.fp_monitor_state("main", false) is_used = true
elseif engine.monitors.flow == device then engine.monitors.main = nil
if engine.ui.flow_display ~= nil then engine.ui.main_display = nil
-- delete element tree and clear root UI elements
engine.ui.flow_display.delete()
end
is_used = true iocontrol.fp_monitor_state("main", false)
engine.monitors.flow = nil elseif engine.monitors.flow == device then
engine.ui.flow_display = nil if engine.ui.flow_display ~= nil then
-- delete element tree and clear root UI elements
engine.ui.flow_display.delete()
end
iocontrol.fp_monitor_state("flow", false) is_used = true
else engine.monitors.flow = nil
for idx, monitor in pairs(engine.monitors.unit_displays) do engine.ui.flow_display = nil
if monitor == device then
if engine.ui.unit_displays[idx] ~= nil then
engine.ui.unit_displays[idx].delete()
end
is_used = true iocontrol.fp_monitor_state("flow", false)
engine.monitors.unit_displays[idx] = nil else
engine.ui.unit_displays[idx] = nil for idx, monitor in pairs(engine.monitors.unit_displays) do
if monitor == device then
iocontrol.fp_monitor_state(idx, false) if engine.ui.unit_displays[idx] ~= nil then
break engine.ui.unit_displays[idx].delete()
end end
is_used = true
engine.monitors.unit_displays[idx] = nil
engine.ui.unit_displays[idx] = nil
iocontrol.fp_monitor_state(idx, false)
break
end end
end end
end end
@@ -293,52 +331,29 @@ end
function renderer.handle_reconnect(name, device) function renderer.handle_reconnect(name, device)
local is_used = false local is_used = false
if engine.monitors ~= nil then if not engine.monitors then return false end
if engine.monitors.primary_name == name then
is_used = true
_init_display(device)
engine.monitors.primary = device
local disp_x, disp_y = engine.monitors.primary.getSize() -- note: handle_resize is a more adaptive way of re-initializing a connected monitor
engine.dmesg_window.reposition(1, 1, disp_x, disp_y, engine.monitors.primary) -- since it can handle a monitor being reconnected that isn't the right size
if engine.ui_ready and (engine.ui.main_display == nil) then if engine.monitors.main_name == name then
engine.dmesg_window.setVisible(false) is_used = true
engine.monitors.main = device
engine.ui.main_display = DisplayBox{window=device,fg_bg=style.root} renderer.handle_resize(name)
main_view(engine.ui.main_display) elseif engine.monitors.flow_name == name then
else is_used = true
engine.dmesg_window.setVisible(true) engine.monitors.flow = device
engine.dmesg_window.redraw()
end
iocontrol.fp_monitor_state("main", true) renderer.handle_resize(name)
elseif engine.monitors.flow_name == name then else
is_used = true for idx, monitor in ipairs(engine.monitors.unit_name_map) do
_init_display(device) if monitor == name then
engine.monitors.flow = device is_used = true
engine.monitors.unit_displays[idx] = device
if engine.ui_ready and (engine.ui.flow_display == nil) then renderer.handle_resize(name)
engine.ui.flow_display = DisplayBox{window=device,fg_bg=style.root} break
flow_view(engine.ui.flow_display)
end
iocontrol.fp_monitor_state("flow", true)
else
for idx, monitor in ipairs(engine.monitors.unit_name_map) do
if monitor == name then
is_used = true
_init_display(device)
engine.monitors.unit_displays[idx] = device
if engine.ui_ready and (engine.ui.unit_displays[idx] == nil) then
engine.ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
unit_view(engine.ui.unit_displays[idx], idx)
end
iocontrol.fp_monitor_state(idx, true)
break
end
end end
end end
end end
@@ -346,6 +361,142 @@ function renderer.handle_reconnect(name, device)
return is_used return is_used
end end
-- handle a monitor being resized<br>
-- returns if this monitor is assigned + if the assigned screen still fits
---@param name string monitor name
---@return boolean is_used, boolean is_ok
function renderer.handle_resize(name)
local is_used = false
local is_ok = true
local ui = engine.ui
if not engine.monitors then return false, false end
if engine.monitors.main_name == name and engine.monitors.main then
local device = engine.monitors.main ---@type table
-- this is necessary if the bottom left block was broken and on reconnect
_init_display(device)
is_used = true
-- resize dmesg window if needed, but don't make it thinner
local disp_w, disp_h = engine.monitors.main.getSize()
local dmsg_w, _ = engine.dmesg_window.getSize()
engine.dmesg_window.reposition(1, 1, math.max(disp_w, dmsg_w), disp_h, engine.monitors.main)
if ui.main_display then
ui.main_display.delete()
ui.main_display = nil
end
iocontrol.fp_monitor_state("main", true)
engine.dmesg_window.setVisible(not engine.ui_ready)
if engine.ui_ready then
local draw_start = util.time_ms()
local ok = pcall(function ()
ui.main_display = DisplayBox{window=device,fg_bg=style.root}
main_view(ui.main_display)
end)
if ok then
log_render("main view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
else
if ui.main_display then
ui.main_display.delete()
ui.main_display = nil
end
_print_too_small(device)
iocontrol.fp_monitor_state("main", false)
is_ok = false
end
else engine.dmesg_window.redraw() end
elseif engine.monitors.flow_name == name and engine.monitors.flow then
local device = engine.monitors.flow ---@type table
-- this is necessary if the bottom left block was broken and on reconnect
_init_display(device)
is_used = true
if ui.flow_display then
ui.flow_display.delete()
ui.flow_display = nil
end
iocontrol.fp_monitor_state("flow", true)
if engine.ui_ready then
local draw_start = util.time_ms()
local ok = pcall(function ()
ui.flow_display = DisplayBox{window=device,fg_bg=style.root}
flow_view(ui.flow_display)
end)
if ok then
log_render("flow view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
else
if ui.flow_display then
ui.flow_display.delete()
ui.flow_display = nil
end
_print_too_small(device)
iocontrol.fp_monitor_state("flow", false)
is_ok = false
end
end
else
for idx, monitor in ipairs(engine.monitors.unit_name_map) do
local device = engine.monitors.unit_displays[idx]
if monitor == name and device then
-- this is necessary if the bottom left block was broken and on reconnect
_init_display(device)
is_used = true
if ui.unit_displays[idx] then
ui.unit_displays[idx].delete()
ui.unit_displays[idx] = nil
end
iocontrol.fp_monitor_state(idx, true)
if engine.ui_ready then
local draw_start = util.time_ms()
local ok = pcall(function ()
ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
unit_view(ui.unit_displays[idx], idx)
end)
if ok then
log_render("unit " .. idx .. " view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms")
else
if ui.unit_displays[idx] then
ui.unit_displays[idx].delete()
ui.unit_displays[idx] = nil
end
_print_too_small(device)
iocontrol.fp_monitor_state(idx, false)
is_ok = false
end
end
break
end
end
end
return is_used, is_ok
end
-- handle a touch event -- handle a touch event
---@param event mouse_interaction|nil ---@param event mouse_interaction|nil
@@ -354,16 +505,15 @@ function renderer.handle_mouse(event)
if engine.fp_ready and event.monitor == "terminal" then if engine.fp_ready and event.monitor == "terminal" then
engine.ui.front_panel.handle_mouse(event) engine.ui.front_panel.handle_mouse(event)
elseif engine.ui_ready then elseif engine.ui_ready then
if event.monitor == engine.monitors.primary_name then if event.monitor == engine.monitors.main_name then
engine.ui.main_display.handle_mouse(event) if engine.ui.main_display then engine.ui.main_display.handle_mouse(event) end
elseif event.monitor == engine.monitors.flow_name then elseif event.monitor == engine.monitors.flow_name then
engine.ui.flow_display.handle_mouse(event) if engine.ui.flow_display then engine.ui.flow_display.handle_mouse(event) end
else else
for id, monitor in ipairs(engine.monitors.unit_name_map) do for id, monitor in ipairs(engine.monitors.unit_name_map) do
if event.monitor == monitor then local display = engine.ui.unit_displays[id]
local layout = engine.ui.unit_displays[id] ---@type graphics_element if event.monitor == monitor and display then
layout.handle_mouse(event) if display then display.handle_mouse(event) end
break
end end
end end
end end

View File

@@ -8,7 +8,7 @@ local iocontrol = require("coordinator.iocontrol")
local pocket = {} local pocket = {}
local PROTOCOL = comms.PROTOCOL local PROTOCOL = comms.PROTOCOL
-- local CRDN_TYPE = comms.CRDN_TYPE local CRDN_TYPE = comms.CRDN_TYPE
local MGMT_TYPE = comms.MGMT_TYPE local MGMT_TYPE = comms.MGMT_TYPE
-- retry time constants in ms -- retry time constants in ms
@@ -73,18 +73,18 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
end end
-- send a CRDN packet -- send a CRDN packet
-----@param msg_type CRDN_TYPE ---@param msg_type CRDN_TYPE
-----@param msg table ---@param msg table
-- local function _send(msg_type, msg) local function _send(msg_type, msg)
-- local s_pkt = comms.scada_packet() local s_pkt = comms.scada_packet()
-- local c_pkt = comms.crdn_packet() local c_pkt = comms.crdn_packet()
-- c_pkt.make(msg_type, msg) c_pkt.make(msg_type, msg)
-- s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable()) s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
-- out_queue.push_packet(s_pkt) out_queue.push_packet(s_pkt)
-- self.seq_num = self.seq_num + 1 self.seq_num = self.seq_num + 1
-- end end
-- send a SCADA management packet -- send a SCADA management packet
---@param msg_type MGMT_TYPE ---@param msg_type MGMT_TYPE
@@ -120,8 +120,44 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
---@cast pkt crdn_frame ---@cast pkt crdn_frame
local db = iocontrol.get_db()
-- handle packet by type -- handle packet by type
if pkt.type == nil then if pkt.type == CRDN_TYPE.API_GET_FAC then
local fac = db.facility
local data = {
fac.all_sys_ok,
fac.rtu_count,
fac.radiation,
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active },
util.table_len(fac.tank_data_tbl),
fac.induction_data_tbl[1] ~= nil,
fac.sps_data_tbl[1] ~= nil,
}
_send(CRDN_TYPE.API_GET_FAC, data)
elseif pkt.type == CRDN_TYPE.API_GET_UNIT then
if pkt.length == 1 and type(pkt.data[1]) == "number" then
local u = db.units[pkt.data[1]] ---@type ioctl_unit
if u then
local data = {
u.unit_id,
u.connected,
u.rtu_hw,
u.alarms,
u.annunciator,
u.reactor_data,
u.boiler_data_tbl,
u.turbine_data_tbl,
u.tank_data_tbl
}
_send(CRDN_TYPE.API_GET_UNIT, data)
end
end
else else
log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type) log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type)
end end

View File

@@ -7,30 +7,29 @@ require("/initenv").init_env()
local comms = require("scada-common.comms") local comms = require("scada-common.comms")
local crash = require("scada-common.crash") local crash = require("scada-common.crash")
local log = require("scada-common.log") local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue")
local network = require("scada-common.network") local network = require("scada-common.network")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core")
local configure = require("coordinator.configure") local configure = require("coordinator.configure")
local coordinator = require("coordinator.coordinator") local coordinator = require("coordinator.coordinator")
local iocontrol = require("coordinator.iocontrol") local iocontrol = require("coordinator.iocontrol")
local renderer = require("coordinator.renderer") local renderer = require("coordinator.renderer")
local sounder = require("coordinator.sounder") local sounder = require("coordinator.sounder")
local threads = require("coordinator.threads")
local apisessions = require("coordinator.session.apisessions") local COORDINATOR_VERSION = "v1.4.6"
local COORDINATOR_VERSION = "v1.2.2" local CHUNK_LOAD_DELAY_S = 30.0
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
local log_graphics = coordinator.log_graphics local log_render = coordinator.log_render
local log_sys = coordinator.log_sys local log_sys = coordinator.log_sys
local log_boot = coordinator.log_boot local log_boot = coordinator.log_boot
local log_comms = coordinator.log_comms local log_comms = coordinator.log_comms
local log_crypto = coordinator.log_crypto local log_crypto = coordinator.log_crypto
---------------------------------------- ----------------------------------------
@@ -40,15 +39,47 @@ local log_crypto = coordinator.log_crypto
-- mount connected devices (required for monitor setup) -- mount connected devices (required for monitor setup)
ppm.mount_all() ppm.mount_all()
local wait_on_load = true
local loaded, monitors = coordinator.load_config() local loaded, monitors = coordinator.load_config()
-- if the computer just started, its chunk may have just loaded (...or the user rebooted)
-- if monitor config failed, maybe an adjacent chunk containing all or part of a monitor has not loaded yet, so keep trying
while wait_on_load and loaded == 2 and os.clock() < CHUNK_LOAD_DELAY_S do
term.clear()
term.setCursorPos(1, 1)
println("There was a monitor configuration problem at boot.\n")
println("Startup will keep trying every 2s in case of chunk load delays.\n")
println(util.sprintf("The configurator will be started in %ds if all attempts fail.\n", math.max(0, CHUNK_LOAD_DELAY_S - os.clock())))
println("(click to skip to the configurator)")
local timer_id = util.start_timer(2)
while true do
local event, param1 = util.pull_event()
if event == "timer" and param1 == timer_id then
-- remount and re-attempt
ppm.mount_all()
loaded, monitors = coordinator.load_config()
break
elseif event == "mouse_click" or event == "terminate" then
wait_on_load = false
break
end
end
end
if loaded ~= 0 then if loaded ~= 0 then
-- try to reconfigure (user action) -- try to reconfigure (user action)
local success, error = configure.configure(loaded, monitors) local success, error = configure.configure(loaded, monitors)
if success then if success then
loaded, monitors = coordinator.load_config() loaded, monitors = coordinator.load_config()
assert(loaded == 0, util.trinary(loaded == 1, "failed to load valid configuration", "monitor configuration invalid")) if loaded ~= 0 then
println(util.trinary(loaded == 2, "monitor configuration invalid", "failed to load a valid configuration") .. ", please reconfigure")
return
end
else else
assert(success, "coordinator configuration error: " .. error) println("configuration error: " .. error)
return
end end
end end
@@ -69,6 +100,7 @@ log.info("========================================")
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
crash.set_env("coordinator", COORDINATOR_VERSION) crash.set_env("coordinator", COORDINATOR_VERSION)
crash.dbg_log_env()
---------------------------------------- ----------------------------------------
-- main application -- main application
@@ -79,14 +111,14 @@ local function main()
-- system startup -- system startup
---------------------------------------- ----------------------------------------
-- re-mount devices now that logging is ready -- log mounts now since mounting was done before logging was ready
ppm.mount_all() ppm.log_mounts()
-- report versions/init fp PSIL -- report versions/init fp PSIL
iocontrol.init_fp(COORDINATOR_VERSION, comms.version) iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
-- init renderer -- init renderer
renderer.legacy_disable_flow_view(config.DisableFlowView) renderer.configure(config)
renderer.set_displays(monitors) renderer.set_displays(monitors)
renderer.init_displays() renderer.init_displays()
renderer.init_dmesg() renderer.init_dmesg()
@@ -94,16 +126,58 @@ local function main()
-- lets get started! -- lets get started!
log.info("monitors ready, dmesg output incoming...") log.info("monitors ready, dmesg output incoming...")
log_graphics("displays connected and reset") log_render("displays connected and reset")
log_sys("system start on " .. os.date("%c")) log_sys("system start on " .. os.date("%c"))
log_boot("starting " .. COORDINATOR_VERSION) log_boot("starting " .. COORDINATOR_VERSION)
----------------------------------------
-- memory allocation
----------------------------------------
-- shared memory across threads
---@class crd_shared_memory
local __shared_memory = {
-- time and date format for display
date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y"),
-- coordinator system state flags
---@class crd_state
crd_state = {
fp_ok = false,
ui_ok = true, -- default true, used to abort on fail
link_fail = false,
shutdown = false
},
-- core coordinator devices
crd_dev = {
speaker = ppm.get_device("speaker"),
modem = ppm.get_wireless_modem()
},
-- system objects
crd_sys = {
nic = nil, ---@type nic
coord_comms = nil, ---@type coord_comms
conn_watchdog = nil ---@type watchdog
},
-- message queues
q = {
mq_render = mqueue.new()
}
}
local smem_dev = __shared_memory.crd_dev
local smem_sys = __shared_memory.crd_sys
local crd_state = __shared_memory.crd_state
---------------------------------------- ----------------------------------------
-- setup alarm sounder subsystem -- setup alarm sounder subsystem
---------------------------------------- ----------------------------------------
local speaker = ppm.get_device("speaker") if smem_dev.speaker == nil then
if speaker == nil then
log_boot("annunciator alarm speaker not found") log_boot("annunciator alarm speaker not found")
println("startup> speaker not found") println("startup> speaker not found")
log.fatal("no annunciator alarm speaker found") log.fatal("no annunciator alarm speaker found")
@@ -111,7 +185,7 @@ local function main()
else else
local sounder_start = util.time_ms() local sounder_start = util.time_ms()
log_boot("annunciator alarm speaker connected") log_boot("annunciator alarm speaker connected")
sounder.init(speaker, config.SpeakerVolume) sounder.init(smem_dev.speaker, config.SpeakerVolume)
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms") log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
log_sys("annunciator alarm configured") log_sys("annunciator alarm configured")
iocontrol.fp_has_speaker(true) iocontrol.fp_has_speaker(true)
@@ -128,8 +202,7 @@ local function main()
end end
-- get the communications modem -- get the communications modem
local modem = ppm.get_wireless_modem() if smem_dev.modem == nil then
if modem == nil then
log_comms("wireless modem not found") log_comms("wireless modem not found")
println("startup> wireless modem not found") println("startup> wireless modem not found")
log.fatal("no wireless modem on startup") log.fatal("no wireless modem on startup")
@@ -140,238 +213,54 @@ local function main()
end end
-- create connection watchdog -- create connection watchdog
local conn_watchdog = util.new_watchdog(config.SVR_Timeout) smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout)
conn_watchdog.cancel() smem_sys.conn_watchdog.cancel()
log.debug("startup> conn watchdog created") log.debug("startup> conn watchdog created")
-- create network interface then setup comms -- create network interface then setup comms
local nic = network.nic(modem) smem_sys.nic = network.nic(smem_dev.modem)
local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, conn_watchdog) smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
log.debug("startup> comms init") log.debug("startup> comms init")
log_comms("comms initialized") log_comms("comms initialized")
-- base loop clock (2Hz, 10 ticks)
local MAIN_CLOCK = 0.5
local loop_clock = util.new_clock(MAIN_CLOCK)
---------------------------------------- ----------------------------------------
-- start front panel & UI start function -- start front panel
---------------------------------------- ----------------------------------------
log_graphics("starting front panel UI...") log_render("starting front panel UI...")
local fp_ok, fp_message = renderer.try_start_fp() local fp_message
if not fp_ok then crd_state.fp_ok, fp_message = renderer.try_start_fp()
log_graphics(util.c("front panel UI error: ", fp_message)) if not crd_state.fp_ok then
log_render(util.c("front panel UI error: ", fp_message))
println_ts("front panel UI creation failed") println_ts("front panel UI creation failed")
log.fatal(util.c("front panel GUI render failed with error ", fp_message)) log.fatal(util.c("front panel GUI render failed with error ", fp_message))
return return
else log_graphics("front panel ready") end else log_render("front panel ready") end
-- start up the main UI
---@return boolean ui_ok started ok
local function start_main_ui()
log_graphics("starting main UI...")
local draw_start = util.time_ms()
local ui_ok, ui_message = renderer.try_start_ui()
if not ui_ok then
log_graphics(util.c("main UI error: ", ui_message))
log.fatal(util.c("main GUI render failed with error ", ui_message))
else
log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
end
return ui_ok
end
---------------------------------------- ----------------------------------------
-- main event loop -- start system
---------------------------------------- ----------------------------------------
local link_failed = false -- init threads
local ui_ok = true local main_thread = threads.thread__main(__shared_memory)
local date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y") local render_thread = threads.thread__render(__shared_memory)
-- start clock log.info("startup> completed")
loop_clock.start()
log_sys("system started successfully") -- run threads
parallel.waitForAll(main_thread.p_exec, render_thread.p_exec)
-- main event loop
while true do
local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event
if event == "peripheral_detach" then
local type, device = ppm.handle_unmount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
-- we only really care if this is our wireless modem
-- if it is another modem, handle other peripheral losses separately
if nic.is_modem(device) then
nic.disconnect()
log_sys("comms modem disconnected")
local other_modem = ppm.get_wireless_modem()
if other_modem then
log_sys("found another wireless modem, using it for comms")
nic.connect(other_modem)
else
-- close out main UI
renderer.close_ui()
-- alert user to status
log_sys("awaiting comms modem reconnect...")
iocontrol.fp_has_modem(false)
end
else
log_sys("non-comms modem disconnected")
end
elseif type == "monitor" then
if renderer.handle_disconnect(device) then
log_sys("lost a configured monitor")
else
log_sys("lost an unused monitor")
end
elseif type == "speaker" then
log_sys("lost alarm sounder speaker")
iocontrol.fp_has_speaker(false)
end
end
elseif event == "peripheral" then
local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
if device.isWireless() and not nic.is_connected() then
-- reconnected modem
log_sys("comms modem reconnected")
nic.connect(device)
iocontrol.fp_has_modem(true)
elseif device.isWireless() then
log.info("unused wireless modem reconnected")
else
log_sys("wired modem reconnected")
end
elseif type == "monitor" then
if renderer.handle_reconnect(param1, device) then
log_sys(util.c("configured monitor ", param1, " reconnected"))
else
log_sys(util.c("unused monitor ", param1, " connected"))
end
elseif type == "speaker" then
log_sys("alarm sounder speaker reconnected")
sounder.reconnect(device)
iocontrol.fp_has_speaker(true)
end
end
elseif event == "timer" then
if loop_clock.is_clock(param1) then
-- main loop tick
-- toggle heartbeat
iocontrol.heartbeat()
-- maintain connection
if nic.is_connected() then
local ok, start_ui = coord_comms.try_connect()
if not ok then
link_failed = true
log_sys("supervisor connection failed, shutting down...")
log.fatal("failed to connect to supervisor")
break
elseif start_ui then
log_sys("supervisor connected, proceeding to main UI start")
ui_ok = start_main_ui()
if not ui_ok then break end
end
end
-- iterate sessions
apisessions.iterate_all()
-- free any closed sessions
apisessions.free_all_closed()
-- update date and time string for main display
if coord_comms.is_linked() then
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
end
loop_clock.start()
elseif conn_watchdog.is_timer(param1) then
-- supervisor watchdog timeout
log_comms("supervisor server timeout")
-- close connection, main UI, and stop sounder
coord_comms.close()
renderer.close_ui()
sounder.stop()
else
-- a non-clock/main watchdog timer event
-- check API watchdogs
apisessions.check_all_watchdogs(param1)
-- notify timer callback dispatcher
tcd.handle(param1)
end
elseif event == "modem_message" then
-- got a packet
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
-- handle then check if it was a disconnect
if coord_comms.handle_packet(packet) then
log_comms("supervisor closed connection")
-- close connection, main UI, and stop sounder
coord_comms.close()
renderer.close_ui()
sounder.stop()
end
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
-- handle a mouse event
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
elseif event == "speaker_audio_empty" then
-- handle speaker buffer emptied
sounder.continue()
end
-- check for termination request
if event == "terminate" or ppm.should_terminate() then
-- handle supervisor connection
coord_comms.try_connect(true)
if coord_comms.is_linked() then
log_comms("terminate requested, closing supervisor connection...")
else link_failed = true end
coord_comms.close()
log_comms("supervisor connection closed")
-- handle API sessions
log_comms("closing api sessions...")
apisessions.close_all()
log_comms("api sessions closed")
break
end
end
renderer.close_ui() renderer.close_ui()
renderer.close_fp() renderer.close_fp()
sounder.stop() sounder.stop()
log_sys("system shutdown") log_sys("system shutdown")
if link_failed then println_ts("failed to connect to supervisor") end if crd_state.link_fail then println_ts("failed to connect to supervisor") end
if not ui_ok then println_ts("main UI creation failed") end if not crd_state.ui_ok then println_ts("main UI creation failed") end
-- close on error exit (such as UI error) -- close on error exit (such as UI error)
if coord_comms.is_linked() then coord_comms.close() end if smem_sys.coord_comms.is_linked() then smem_sys.coord_comms.close() end
println_ts("exited") println_ts("exited")
log.info("exited") log.info("exited")

363
coordinator/threads.lua Normal file
View File

@@ -0,0 +1,363 @@
local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue")
local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd")
local util = require("scada-common.util")
local coordinator = require("coordinator.coordinator")
local iocontrol = require("coordinator.iocontrol")
local renderer = require("coordinator.renderer")
local sounder = require("coordinator.sounder")
local apisessions = require("coordinator.session.apisessions")
local core = require("graphics.core")
local log_render = coordinator.log_render
local log_sys = coordinator.log_sys
local log_comms = coordinator.log_comms
local threads = {}
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
local MQ__RENDER_CMD = {
START_MAIN_UI = 1
}
local MQ__RENDER_DATA = {
MON_CONNECT = 1,
MON_DISCONNECT = 2,
MON_RESIZE = 3
}
-- main thread
---@nodiscard
---@param smem crd_shared_memory
function threads.thread__main(smem)
---@class parallel_thread
local public = {}
-- execute thread
function public.exec()
iocontrol.fp_rt_status("main", true)
log.debug("main thread start")
local loop_clock = util.new_clock(MAIN_CLOCK)
-- start clock
loop_clock.start()
log_sys("system started successfully")
-- load in from shared memory
local crd_state = smem.crd_state
local nic = smem.crd_sys.nic
local coord_comms = smem.crd_sys.coord_comms
local conn_watchdog = smem.crd_sys.conn_watchdog
-- event loop
while true do
local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event
if event == "peripheral_detach" then
local type, device = ppm.handle_unmount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
-- we only really care if this is our wireless modem
-- if it is another modem, handle other peripheral losses separately
if nic.is_modem(device) then
nic.disconnect()
log_sys("comms modem disconnected")
local other_modem = ppm.get_wireless_modem()
if other_modem then
log_sys("found another wireless modem, using it for comms")
nic.connect(other_modem)
else
-- close out main UI
renderer.close_ui()
-- alert user to status
log_sys("awaiting comms modem reconnect...")
iocontrol.fp_has_modem(false)
end
else
log_sys("non-comms modem disconnected")
end
elseif type == "monitor" then
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device)
elseif type == "speaker" then
log_sys("lost alarm sounder speaker")
iocontrol.fp_has_speaker(false)
end
end
elseif event == "peripheral" then
local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
if device.isWireless() and not nic.is_connected() then
-- reconnected modem
log_sys("comms modem reconnected")
nic.connect(device)
iocontrol.fp_has_modem(true)
elseif device.isWireless() then
log.info("unused wireless modem reconnected")
else
log_sys("wired modem reconnected")
end
elseif type == "monitor" then
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device })
elseif type == "speaker" then
log_sys("alarm sounder speaker reconnected")
sounder.reconnect(device)
iocontrol.fp_has_speaker(true)
end
end
elseif event == "monitor_resize" then
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1)
elseif event == "timer" then
if loop_clock.is_clock(param1) then
-- main loop tick
-- toggle heartbeat
iocontrol.heartbeat()
-- maintain connection
if nic.is_connected() then
local ok, start_ui = coord_comms.try_connect()
if not ok then
crd_state.link_fail = true
crd_state.shutdown = true
log_sys("supervisor connection failed, shutting down...")
log.fatal("failed to connect to supervisor")
break
elseif start_ui then
log_sys("supervisor connected, dispatching main UI start")
smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI)
end
end
-- iterate sessions and free any closed ones
apisessions.iterate_all()
apisessions.free_all_closed()
if renderer.ui_ready() then
-- update clock used on main and flow monitors
iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format))
end
loop_clock.start()
elseif conn_watchdog.is_timer(param1) then
-- supervisor watchdog timeout
log_comms("supervisor server timeout")
-- close connection, main UI, and stop sounder
coord_comms.close()
renderer.close_ui()
sounder.stop()
else
-- a non-clock/main watchdog timer event
-- check API watchdogs
apisessions.check_all_watchdogs(param1)
-- notify timer callback dispatcher
tcd.handle(param1)
end
elseif event == "modem_message" then
-- got a packet
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
-- handle then check if it was a disconnect
if coord_comms.handle_packet(packet) then
log_comms("supervisor closed connection")
-- close connection, main UI, and stop sounder
coord_comms.close()
renderer.close_ui()
sounder.stop()
end
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
-- handle a mouse event
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
elseif event == "speaker_audio_empty" then
-- handle speaker buffer emptied
sounder.continue()
end
-- check for termination request or UI crash
if event == "terminate" or ppm.should_terminate() then
crd_state.shutdown = true
log.info("terminate requested, main thread exiting")
elseif not crd_state.ui_ok then
crd_state.shutdown = true
log.info("terminating due to fatal UI error")
end
if crd_state.shutdown then
-- handle closing supervisor connection
coord_comms.try_connect(true)
if coord_comms.is_linked() then
log_comms("closing supervisor connection...")
else crd_state.link_fail = true end
coord_comms.close()
log_comms("supervisor connection closed")
-- handle API sessions
log_comms("closing api sessions...")
apisessions.close_all()
log_comms("api sessions closed")
break
end
end
end
-- execute the thread in a protected mode, retrying it on return if not shutting down
function public.p_exec()
local crd_state = smem.crd_state
while not crd_state.shutdown do
local status, result = pcall(public.exec)
if status == false then
log.fatal(util.strval(result))
end
iocontrol.fp_rt_status("main", false)
-- if status is true, then we are probably exiting, so this won't matter
-- this thread cannot be slept because it will miss events (namely "terminate")
if not crd_state.shutdown then
log.info("main thread restarting now...")
end
end
end
return public
end
-- coordinator renderer thread, tasked with long duration re-draws
---@nodiscard
---@param smem crd_shared_memory
function threads.thread__render(smem)
---@class parallel_thread
local public = {}
-- execute thread
function public.exec()
iocontrol.fp_rt_status("render", true)
log.debug("render thread start")
-- load in from shared memory
local crd_state = smem.crd_state
local render_queue = smem.q.mq_render
local last_update = util.time()
-- thread loop
while true do
-- check for messages in the message queue
while render_queue.ready() and not crd_state.shutdown do
local msg = render_queue.pop()
if msg ~= nil then
if msg.qtype == mqueue.TYPE.COMMAND then
-- received a command
if msg.message == MQ__RENDER_CMD.START_MAIN_UI then
-- stop the UI if it was already started
-- this may occur on a quick supervisor disconnect -> connect
if renderer.ui_ready() then
log_render("closing main UI before executing new request to start")
renderer.close_ui()
end
-- start up the main UI
log_render("starting main UI...")
local draw_start = util.time_ms()
local ui_message
crd_state.ui_ok, ui_message = renderer.try_start_ui()
if not crd_state.ui_ok then
log_render(util.c("main UI error: ", ui_message))
log.fatal(util.c("main GUI render failed with error ", ui_message))
else
log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
end
end
elseif msg.qtype == mqueue.TYPE.DATA then
-- received data
local cmd = msg.message ---@type queue_data
if cmd.key == MQ__RENDER_DATA.MON_CONNECT then
-- monitor connected
if renderer.handle_reconnect(cmd.val.name, cmd.val.device) then
log_sys(util.c("configured monitor ", cmd.val.name, " reconnected"))
else
log_sys(util.c("unused monitor ", cmd.val.name, " connected"))
end
elseif cmd.key == MQ__RENDER_DATA.MON_DISCONNECT then
-- monitor disconnected
if renderer.handle_disconnect(cmd.val) then
log_sys("lost a configured monitor")
else
log_sys("lost an unused monitor")
end
elseif cmd.key == MQ__RENDER_DATA.MON_RESIZE then
-- monitor resized
local is_used, is_ok = renderer.handle_resize(cmd.val)
if is_used then
log_sys(util.c("configured monitor ", cmd.val, " resized, ", util.trinary(is_ok, "display fits", "display does not fit")))
end
end
elseif msg.qtype == mqueue.TYPE.PACKET then
-- received a packet
end
end
-- quick yield
util.nop()
end
-- check for termination request
if crd_state.shutdown then
log.info("render thread exiting")
break
end
-- delay before next check
last_update = util.adaptive_delay(RENDER_SLEEP, last_update)
end
end
-- execute the thread in a protected mode, retrying it on return if not shutting down
function public.p_exec()
local crd_state = smem.crd_state
while not crd_state.shutdown do
local status, result = pcall(public.exec)
if status == false then
log.fatal(util.strval(result))
end
iocontrol.fp_rt_status("render", false)
if not crd_state.shutdown then
log.info("render thread restarting in 5 seconds...")
util.psleep(5)
end
end
end
return public
end
return threads

View File

@@ -1,5 +1,7 @@
local style = require("coordinator.ui.style") local style = require("coordinator.ui.style")
local iocontrol = require("coordinator.iocontrol")
local core = require("graphics.core") local core = require("graphics.core")
local Rectangle = require("graphics.elements.rectangle") local Rectangle = require("graphics.elements.rectangle")
@@ -12,28 +14,31 @@ local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.cpair local cpair = core.cpair
local border = core.border local border = core.border
local text_fg_bg = style.text_colors
-- new boiler view -- new boiler view
---@param root graphics_element parent ---@param root graphics_element parent
---@param x integer top left x ---@param x integer top left x
---@param y integer top left y ---@param y integer top left y
---@param ps psil ps interface ---@param ps psil ps interface
local function new_view(root, x, y, ps) local function new_view(root, x, y, ps)
local text_fg = style.theme.text_fg
local lu_col = style.lu_colors
local db = iocontrol.get_db()
local boiler = Rectangle{parent=root,border=border(1,colors.gray,true),width=31,height=7,x=x,y=y} local boiler = Rectangle{parent=root,border=border(1,colors.gray,true),width=31,height=7,x=x,y=y}
local status = StateIndicator{parent=boiler,x=9,y=1,states=style.boiler.states,value=1,min_width=12} local status = StateIndicator{parent=boiler,x=9,y=1,states=style.boiler.states,value=1,min_width=12}
local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=style.lu_col,label="Temp:",unit="K",format="%10.2f",value=0,width=22,fg_bg=text_fg_bg} local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=22,fg_bg=text_fg}
local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=style.lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg} local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg}
status.register(ps, "computed_status", status.update) status.register(ps, "computed_status", status.update)
temp.register(ps, "temperature", temp.update) temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end)
boil_r.register(ps, "boil_rate", boil_r.update) boil_r.register(ps, "boil_rate", boil_r.update)
TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg}
TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg}
TextBox{parent=boiler,text="S",x=27,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="S",x=27,y=5,height=1,width=1,fg_bg=text_fg}
TextBox{parent=boiler,text="C",x=28,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="C",x=28,y=5,height=1,width=1,fg_bg=text_fg}
local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1} local hcool = VerticalBar{parent=boiler,x=2,y=1,fg_bg=cpair(colors.orange,colors.gray),height=4,width=1}
local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} local water = VerticalBar{parent=boiler,x=3,y=1,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}

View File

@@ -9,6 +9,7 @@ local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox") local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data") local DataIndicator = require("graphics.elements.indicators.data")
local IndicatorLight = require("graphics.elements.indicators.light")
local PowerIndicator = require("graphics.elements.indicators.power") local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state") local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar") local VerticalBar = require("graphics.elements.indicators.vbar")
@@ -18,9 +19,6 @@ local border = core.border
local ALIGN = core.ALIGN local ALIGN = core.ALIGN
local text_fg_bg = style.text_colors
local lu_col = style.lu_colors
-- new induction matrix view -- new induction matrix view
---@param root graphics_element parent ---@param root graphics_element parent
---@param x integer top left x ---@param x integer top left x
@@ -29,57 +27,67 @@ local lu_col = style.lu_colors
---@param ps psil ps interface ---@param ps psil ps interface
---@param id number? matrix ID ---@param id number? matrix ID
local function new_view(root, x, y, data, ps, id) local function new_view(root, x, y, data, ps, id)
local label_fg = style.theme.label_fg
local text_fg = style.theme.text_fg
local lu_col = style.lu_colors
local ind_yel = style.ind_yel
local ind_wht = style.ind_wht
local title = "INDUCTION MATRIX" local title = "INDUCTION MATRIX"
if type(id) == "number" then title = title .. id end if type(id) == "number" then title = title .. id end
local matrix = Div{parent=root,fg_bg=style.root,width=33,height=24,x=x,y=y} local matrix = Div{parent=root,fg_bg=style.root,width=33,height=24,x=x,y=y}
TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=style.lg_gray} -- black has low contrast with dark gray, so if background is black use white instead
TextBox{parent=matrix,text=title,alignment=ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=style.lg_gray} local cutout_fg_bg = cpair(util.trinary(style.theme.bg == colors.black, colors.white, style.theme.bg), colors.gray)
TextBox{parent=matrix,text=" ",width=33,height=1,x=1,y=1,fg_bg=cutout_fg_bg}
TextBox{parent=matrix,text=title,alignment=ALIGN.CENTER,width=33,height=1,x=1,y=2,fg_bg=cutout_fg_bg}
local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3} local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3}
local label_fg_bg = cpair(colors.gray, colors.lightGray) local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14}
local capacity = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg}
local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14} local energy = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg}
local energy = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} local avg_chg = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",value=0,width=26,fg_bg=text_fg}
local capacity = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} local input = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} local avg_in = PowerIndicator{parent=rect,x=7,y=7,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local output = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} local output = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local avg_out = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local avg_chg = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Avg. Chg:",format="%8.2f",value=0,width=26,fg_bg=text_fg_bg} local trans_cap = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Max I/O: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg}
local avg_in = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="Avg. In: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg}
local avg_out = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Avg. Out:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg}
status.register(ps, "computed_status", status.update) status.register(ps, "computed_status", status.update)
energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end) capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end)
input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end) energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end)
output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
avg_chg.register(ps, "avg_charge", avg_chg.update) avg_chg.register(ps, "avg_charge", avg_chg.update)
input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end)
avg_in.register(ps, "avg_inflow", avg_in.update) avg_in.register(ps, "avg_inflow", avg_in.update)
output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
avg_out.register(ps, "avg_outflow", avg_out.update) avg_out.register(ps, "avg_outflow", avg_out.update)
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end)
local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg_bg} local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill: ",format="%7.2f",unit="%",value=0,width=20,fg_bg=text_fg}
local cells = DataIndicator{parent=rect,x=11,y=13,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg}
local cells = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg_bg} local providers = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg}
local providers = DataIndicator{parent=rect,x=11,y=15,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg_bg}
TextBox{parent=rect,text="Transfer Capacity",x=11,y=17,height=1,width=17,fg_bg=label_fg_bg}
local trans_cap = PowerIndicator{parent=rect,x=19,y=18,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg_bg}
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
cells.register(ps, "cells", cells.update) cells.register(ps, "cells", cells.update)
providers.register(ps, "providers", providers.update) providers.register(ps, "providers", providers.update)
fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) local chging = IndicatorLight{parent=rect,x=11,y=16,label="Charging",colors=ind_wht}
local dischg = IndicatorLight{parent=rect,x=11,y=17,label="Discharging",colors=ind_wht}
local max_io = IndicatorLight{parent=rect,x=11,y=18,label="Max I/O Rate",colors=ind_yel}
chging.register(ps, "is_charging", chging.update)
dischg.register(ps, "is_discharging", dischg.update)
max_io.register(ps, "at_max_io", max_io.update)
local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4} local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4}
local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1} local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1}
local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1} local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1}
TextBox{parent=rect,text="FILL",x=2,y=20,height=1,width=4,fg_bg=text_fg_bg} TextBox{parent=rect,text="FILL I/O",x=2,y=20,height=1,width=8,fg_bg=label_fg}
TextBox{parent=rect,text="I/O",x=7,y=20,height=1,width=3,fg_bg=text_fg_bg}
local function calc_saturation(val) local function calc_saturation(val)
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
@@ -90,6 +98,49 @@ local function new_view(root, x, y, data, ps, id)
charge.register(ps, "energy_fill", charge.update) charge.register(ps, "energy_fill", charge.update)
in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end) in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end)
out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end) out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end)
local eta = TextBox{parent=rect,x=11,y=20,width=20,height=1,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box}
eta.register(ps, "eta_ms", function (eta_ms)
local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ")
local seconds = math.abs(eta_ms) / 1000
local minutes = seconds / 60
local hours = minutes / 60
local days = hours / 24
if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then
-- really small or NaN
str = "No ETA"
elseif days < 1000 then
days = math.floor(days)
hours = math.floor(hours % 24)
minutes = math.floor(minutes % 60)
seconds = math.floor(seconds % 60)
if days > 0 then
str = days .. "d"
elseif hours > 0 then
str = hours .. "h " .. minutes .. "m"
elseif minutes > 0 then
str = minutes .. "m " .. seconds .. "s"
elseif seconds > 0 then
str = seconds .. "s"
end
str = pre .. str
else
local years = math.floor(days / 365.25)
if years <= 99999999 then
str = pre .. years .. "y"
else
str = pre .. "eras"
end
end
eta.set_value(str)
end)
end end
return new_view return new_view

View File

@@ -17,33 +17,35 @@ local ALIGN = core.ALIGN
local cpair = core.cpair local cpair = core.cpair
local text_fg_bg = style.text_colors
local lg_wh = style.lg_white
-- create a pocket list entry -- create a pocket list entry
---@param parent graphics_element parent ---@param parent graphics_element parent
---@param id integer PKT session ID ---@param id integer PKT session ID
local function init(parent, id) local function init(parent, id)
local s_hi_box = style.fp_theme.highlight_box
local s_hi_bright = style.fp_theme.highlight_box_bright
local label_fg = style.fp.label_fg
local ps = iocontrol.get_db().fp.ps local ps = iocontrol.get_db().fp.ps
-- root div -- root div
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true} local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.bw_fg_bg} local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=s_hi_bright}
local ps_prefix = "pkt_" .. id .. "_" local ps_prefix = "pkt_" .. id .. "_"
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=text_fg_bg} TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=text_fg_bg,nav_active=cpair(colors.gray,colors.black)} local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=text_fg_bg} TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value) pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value)
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1} TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=lg_wh} local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=label_fg}
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value) pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1} TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_wh} local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=lg_wh} TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update) pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor) pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)

View File

@@ -29,16 +29,6 @@ local cpair = core.cpair
local border = core.border local border = core.border
local bw_fg_bg = style.bw_fg_bg local bw_fg_bg = style.bw_fg_bg
local lu_cpair = style.lu_colors
local hzd_fg_bg = style.hzd_fg_bg
local dis_colors = style.dis_colors
local gry_wht = style.gray_white
local ind_grn = style.ind_grn
local ind_yel = style.ind_yel
local ind_red = style.ind_red
local ind_wht = style.ind_wht
local period = core.flasher.PERIOD local period = core.flasher.PERIOD
@@ -47,6 +37,19 @@ local period = core.flasher.PERIOD
---@param x integer top left x ---@param x integer top left x
---@param y integer top left y ---@param y integer top left y
local function new_view(root, x, y) local function new_view(root, x, y)
local s_hi_box = style.theme.highlight_box
local s_field = style.theme.field_box
local lu_cpair = style.lu_colors
local hzd_fg_bg = style.hzd_fg_bg
local dis_colors = style.dis_colors
local arrow_fg_bg = cpair(style.theme.label, s_hi_box.bkg)
local ind_grn = style.ind_grn
local ind_yel = style.ind_yel
local ind_red = style.ind_red
local ind_wht = style.ind_wht
assert(root.get_height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)") assert(root.get_height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)")
local black = cpair(colors.black, colors.black) local black = cpair(colors.black, colors.black)
@@ -65,7 +68,7 @@ local function new_view(root, x, y)
facility.ack_alarms_ack = ack_a.on_response facility.ack_alarms_ack = ack_a.on_response
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn} local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn}
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=style.ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd}
local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=ind_grn} local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=ind_grn}
local sps = IndicatorLight{parent=main,label="SPS Connected",colors=ind_grn} local sps = IndicatorLight{parent=main,label="SPS Connected",colors=ind_grn}
@@ -103,11 +106,11 @@ local function new_view(root, x, y)
gen_fault.register(facility.ps, "as_gen_fault", gen_fault.update) gen_fault.register(facility.ps, "as_gen_fault", gen_fault.update)
TextBox{parent=main,y=23,text="Radiation",height=1,width=13,fg_bg=style.label} TextBox{parent=main,y=23,text="Radiation",height=1,width=13,fg_bg=style.label}
local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
radiation.register(facility.ps, "radiation", radiation.update) radiation.register(facility.ps, "radiation", radiation.update)
TextBox{parent=main,x=15,y=23,text="Linked RTUs",height=1,width=11,fg_bg=style.label} TextBox{parent=main,x=15,y=23,text="Linked RTUs",height=1,width=11,fg_bg=style.label}
local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=bw_fg_bg} local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=s_field}
rtu_count.register(facility.ps, "rtu_count", rtu_count.update) rtu_count.register(facility.ps, "rtu_count", rtu_count.update)
--------------------- ---------------------
@@ -125,9 +128,9 @@ local function new_view(root, x, y)
local burn_tag = Div{parent=targets,x=1,y=1,width=8,height=4,fg_bg=blk_pur} local burn_tag = Div{parent=targets,x=1,y=1,width=8,height=4,fg_bg=blk_pur}
TextBox{parent=burn_tag,x=2,y=2,text="Burn Target",width=7,height=2} TextBox{parent=burn_tag,x=2,y=2,text="Burn Target",width=7,height=2}
local burn_target = Div{parent=targets,x=9,y=1,width=23,height=3,fg_bg=gry_wht} local burn_target = Div{parent=targets,x=9,y=1,width=23,height=3,fg_bg=s_hi_box}
local b_target = SpinboxNumeric{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg} local b_target = SpinboxNumeric{parent=burn_target,x=11,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=burn_target,x=18,y=2,text="mB/t"} TextBox{parent=burn_target,x=18,y=2,text="mB/t",fg_bg=style.theme.label_fg}
local burn_sum = DataIndicator{parent=targets,x=9,y=4,label="",format="%18.1f",value=0,unit="mB/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn} local burn_sum = DataIndicator{parent=targets,x=9,y=4,label="",format="%18.1f",value=0,unit="mB/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
b_target.register(facility.ps, "process_burn_target", b_target.set_value) b_target.register(facility.ps, "process_burn_target", b_target.set_value)
@@ -136,20 +139,20 @@ local function new_view(root, x, y)
local chg_tag = Div{parent=targets,x=1,y=6,width=8,height=4,fg_bg=blk_pur} local chg_tag = Div{parent=targets,x=1,y=6,width=8,height=4,fg_bg=blk_pur}
TextBox{parent=chg_tag,x=2,y=2,text="Charge Target",width=7,height=2} TextBox{parent=chg_tag,x=2,y=2,text="Charge Target",width=7,height=2}
local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=gry_wht} local chg_target = Div{parent=targets,x=9,y=6,width=23,height=3,fg_bg=s_hi_box}
local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg} local c_target = SpinboxNumeric{parent=chg_target,x=2,y=1,whole_num_precision=15,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=chg_target,x=18,y=2,text="MFE"} TextBox{parent=chg_target,x=18,y=2,text="MFE",fg_bg=style.theme.label_fg}
local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="MFE",commas=true,lu_colors=black,width=23,fg_bg=blk_brn} local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="MFE",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
c_target.register(facility.ps, "process_charge_target", c_target.set_value) c_target.register(facility.ps, "process_charge_target", c_target.set_value)
cur_charge.register(facility.induction_ps_tbl[1], "energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000000) end) cur_charge.register(facility.induction_ps_tbl[1], "avg_charge", function (fe) cur_charge.update(fe / 1000000) end)
local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=blk_pur} local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=blk_pur}
TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2} TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2}
local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=gry_wht} local gen_target = Div{parent=targets,x=9,y=11,width=23,height=3,fg_bg=s_hi_box}
local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg} local g_target = SpinboxNumeric{parent=gen_target,x=8,y=1,whole_num_precision=9,fractional_precision=0,min=0,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=gen_target,x=18,y=2,text="kFE/t"} TextBox{parent=gen_target,x=18,y=2,text="kFE/t",fg_bg=style.theme.label_fg}
local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="kFE/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn} local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="kFE/t",commas=true,lu_colors=black,width=23,fg_bg=blk_brn}
g_target.register(facility.ps, "process_gen_target", g_target.set_value) g_target.register(facility.ps, "process_gen_target", g_target.set_value)
@@ -165,17 +168,17 @@ local function new_view(root, x, y)
for i = 1, 4 do for i = 1, 4 do
local unit local unit
local tag_fg_bg = gry_wht local tag_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
local lim_fg_bg = style.lg_white local lim_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
local ctl_fg = colors.lightGray local label_fg = style.theme.disabled_fg
local cur_fg_bg = style.lg_white local cur_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
local cur_lu = colors.lightGray local cur_lu = style.theme.disabled
if i <= facility.num_units then if i <= facility.num_units then
unit = units[i] ---@type ioctl_unit unit = units[i] ---@type ioctl_unit
tag_fg_bg = cpair(colors.black,colors.lightBlue) tag_fg_bg = cpair(colors.black, colors.lightBlue)
lim_fg_bg = bw_fg_bg lim_fg_bg = s_hi_box
ctl_fg = colors.gray label_fg = style.theme.label_fg
cur_fg_bg = blk_brn cur_fg_bg = blk_brn
cur_lu = colors.black cur_lu = colors.black
end end
@@ -185,9 +188,9 @@ local function new_view(root, x, y)
local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg} local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg}
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2} TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2}
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(ctl_fg,colors.white)} local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=s_hi_box}
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=gry_wht,fg_bg=lim_fg_bg} local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled,fg_bg=lim_fg_bg}
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1} TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1,fg_bg=label_fg}
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg} local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg}
@@ -209,14 +212,14 @@ local function new_view(root, x, y)
local stat_div = Div{parent=proc,width=22,height=24,x=57,y=6} local stat_div = Div{parent=proc,width=22,height=24,x=57,y=6}
for i = 1, 4 do for i = 1, 4 do
local tag_fg_bg = gry_wht local tag_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
local ind_fg_bg = style.lg_white local ind_fg_bg = cpair(style.theme.disabled, s_hi_box.bkg)
local ind_off = colors.lightGray local ind_off = style.theme.disabled
if i <= facility.num_units then if i <= facility.num_units then
tag_fg_bg = cpair(colors.black, colors.cyan) tag_fg_bg = cpair(colors.black, colors.cyan)
ind_fg_bg = bw_fg_bg ind_fg_bg = cpair(style.theme.text, s_hi_box.bkg)
ind_off = colors.gray ind_off = style.ind_hi_box_bg
end end
local _y = ((i - 1) * 5) + 1 local _y = ((i - 1) * 5) + 1
@@ -225,8 +228,8 @@ local function new_view(root, x, y)
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2} TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2}
local lights = Div{parent=stat_div,x=9,y=_y,width=14,height=4,fg_bg=ind_fg_bg} local lights = Div{parent=stat_div,x=9,y=_y,width=14,height=4,fg_bg=ind_fg_bg}
local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,ind_off)} local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(ind_grn.fgd,ind_off)}
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,ind_off),flash=true,period=period.BLINK_250_MS} local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(ind_red.fgd,ind_off),flash=true,period=period.BLINK_250_MS}
if i <= facility.num_units then if i <= facility.num_units then
local unit = units[i] ---@type ioctl_unit local unit = units[i] ---@type ioctl_unit
@@ -241,18 +244,18 @@ local function new_view(root, x, y)
------------------------- -------------------------
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" } local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.gray,colors.white),select_color=colors.purple} local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
mode.register(facility.ps, "process_mode", mode.set_value) mode.register(facility.ps, "process_mode", mode.set_value)
local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg} local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg}
local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=bw_fg_bg} local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=bw_fg_bg}
local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=gry_wht} local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.white)}
stat_line_1.register(facility.ps, "status_line_1", stat_line_1.set_value) stat_line_1.register(facility.ps, "status_line_1", stat_line_1.set_value)
stat_line_2.register(facility.ps, "status_line_2", stat_line_2.set_value) stat_line_2.register(facility.ps, "status_line_2", stat_line_2.set_value)
local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=gry_wht} local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=s_hi_box}
-- save the automatic process control configuration without starting -- save the automatic process control configuration without starting
local function _save_cfg() local function _save_cfg()
@@ -327,40 +330,36 @@ local function new_view(root, x, y)
local waste_sel = Div{parent=proc,width=21,height=24,x=81,y=1} local waste_sel = Div{parent=proc,width=21,height=24,x=81,y=1}
TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=blk_brn} local cutout_fg_bg = cpair(style.theme.bg, colors.brown)
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.brown)}
TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=cutout_fg_bg}
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=ALIGN.CENTER,width=21,height=1,x=1,y=2,fg_bg=cutout_fg_bg}
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3} local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17} local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17}
status.register(facility.ps, "current_waste_product", status.update) status.register(facility.ps, "current_waste_product", status.update)
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.gray,colors.white),select_color=colors.brown} local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown}
local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,colors.black)}
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value) waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=ind_wht} local fb_active = IndicatorLight{parent=rect,x=2,y=7,label="Fallback Active",colors=ind_wht}
local sps_disabled = IndicatorLight{parent=rect,x=2,y=8,label="SPS Disabled LC",colors=ind_yel}
fb_active.register(facility.ps, "pu_fallback_active", fb_active.update) fb_active.register(facility.ps, "pu_fallback_active", fb_active.update)
sps_disabled.register(facility.ps, "sps_disabled_low_power", sps_disabled.update)
TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label} local pu_fallback = Checkbox{parent=rect,x=2,y=10,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label} TextBox{parent=rect,x=2,y=12,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=style.label}
local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label} local lc_sps = Checkbox{parent=rect,x=2,y=16,label="Low Charge SPS",callback=process.set_sps_low_power,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)}
local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12d",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
pu_rate.register(facility.ps, "pu_rate", pu_rate.update) TextBox{parent=rect,x=2,y=18,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=style.label}
po_rate.register(facility.ps, "po_rate", po_rate.update)
am_rate.register(facility.ps, "am_rate", am_rate.update)
local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17} pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
lc_sps.register(facility.ps, "process_sps_low_power", lc_sps.set_value)
sna_count.register(facility.ps, "sna_count", sna_count.update)
end end
return new_view return new_view

View File

@@ -1,5 +1,7 @@
local types = require("scada-common.types") local types = require("scada-common.types")
local iocontrol = require("coordinator.iocontrol")
local style = require("coordinator.ui.style") local style = require("coordinator.ui.style")
local core = require("graphics.core") local core = require("graphics.core")
@@ -14,35 +16,37 @@ local StateIndicator = require("graphics.elements.indicators.state")
local cpair = core.cpair local cpair = core.cpair
local border = core.border local border = core.border
local text_fg_bg = style.text_colors
local lu_col = style.lu_colors
-- create new reactor view -- create new reactor view
---@param root graphics_element parent ---@param root graphics_element parent
---@param x integer top left x ---@param x integer top left x
---@param y integer top left y ---@param y integer top left y
---@param ps psil ps interface ---@param ps psil ps interface
local function new_view(root, x, y, ps) local function new_view(root, x, y, ps)
local reactor = Rectangle{parent=root,border=border(1, colors.gray, true),width=30,height=7,x=x,y=y} local text_fg = style.theme.text_fg
local lu_col = style.lu_colors
local db = iocontrol.get_db()
local reactor = Rectangle{parent=root,border=border(1,colors.gray,true),width=30,height=7,x=x,y=y}
local status = StateIndicator{parent=reactor,x=6,y=1,states=style.reactor.states,value=1,min_width=16} local status = StateIndicator{parent=reactor,x=6,y=1,states=style.reactor.states,value=1,min_width=16}
local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg} local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg} local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.2f",value=0,width=26,fg_bg=text_fg}
local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg_bg} local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg}
status.register(ps, "computed_status", status.update) status.register(ps, "computed_status", status.update)
core_temp.register(ps, "temp", core_temp.update) core_temp.register(ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
burn_r.register(ps, "act_burn_rate", burn_r.update) burn_r.register(ps, "act_burn_rate", burn_r.update)
heating_r.register(ps, "heating_rate", heating_r.update) heating_r.register(ps, "heating_rate", heating_r.update)
local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y} local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y}
TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,height=1,fg_bg=text_fg_bg} TextBox{parent=reactor_fills,text="FUEL",x=2,y=1,height=1,fg_bg=text_fg}
TextBox{parent=reactor_fills,text="COOL",x=2,y=2,height=1,fg_bg=text_fg_bg} TextBox{parent=reactor_fills,text="COOL",x=2,y=2,height=1,fg_bg=text_fg}
TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg_bg} TextBox{parent=reactor_fills,text="HCOOL",x=2,y=4,height=1,fg_bg=text_fg}
TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg_bg} TextBox{parent=reactor_fills,text="WASTE",x=2,y=5,height=1,fg_bg=text_fg}
local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(colors.black,colors.gray),height=1,width=14} local fuel = HorizontalBar{parent=reactor_fills,x=8,y=1,show_percent=true,bar_fg_bg=cpair(style.theme.fuel_color,colors.gray),height=1,width=14}
local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=14} local ccool = HorizontalBar{parent=reactor_fills,x=8,y=2,show_percent=true,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=14}
local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.white,colors.gray),height=1,width=14} local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.white,colors.gray),height=1,width=14}
local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14}

View File

@@ -15,20 +15,20 @@ local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.cpair local cpair = core.cpair
local border = core.border local border = core.border
local text_fg_bg = style.text_colors
local lu_col = style.lu_colors
-- new turbine view -- new turbine view
---@param root graphics_element parent ---@param root graphics_element parent
---@param x integer top left x ---@param x integer top left x
---@param y integer top left y ---@param y integer top left y
---@param ps psil ps interface ---@param ps psil ps interface
local function new_view(root, x, y, ps) local function new_view(root, x, y, ps)
local text_fg = style.theme.text_fg
local lu_col = style.lu_colors
local turbine = Rectangle{parent=root,border=border(1,colors.gray,true),width=23,height=7,x=x,y=y} local turbine = Rectangle{parent=root,border=border(1,colors.gray,true),width=23,height=7,x=x,y=y}
local status = StateIndicator{parent=turbine,x=7,y=1,states=style.turbine.states,value=1,min_width=12} local status = StateIndicator{parent=turbine,x=7,y=1,states=style.turbine.states,value=1,min_width=12}
local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,rate=true,width=16,fg_bg=text_fg_bg} local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,rate=true,width=16,fg_bg=text_fg}
local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg}
status.register(ps, "computed_status", status.update) status.register(ps, "computed_status", status.update)
prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end) prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end)
@@ -37,8 +37,8 @@ local function new_view(root, x, y, ps)
local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1}
local energy = VerticalBar{parent=turbine,x=3,y=1,fg_bg=cpair(colors.green,colors.gray),height=4,width=1} local energy = VerticalBar{parent=turbine,x=3,y=1,fg_bg=cpair(colors.green,colors.gray),height=4,width=1}
TextBox{parent=turbine,text="S",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=turbine,text="S",x=2,y=5,height=1,width=1,fg_bg=text_fg}
TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg}
steam.register(ps, "steam_fill", steam.update) steam.register(ps, "steam_fill", steam.update)
energy.register(ps, "energy_fill", energy.update) energy.register(ps, "energy_fill", energy.update)

View File

@@ -3,6 +3,7 @@
-- --
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol") local iocontrol = require("coordinator.iocontrol")
@@ -34,25 +35,33 @@ local cpair = core.cpair
local border = core.border local border = core.border
local bw_fg_bg = style.bw_fg_bg local bw_fg_bg = style.bw_fg_bg
local lu_cpair = style.lu_colors
local hzd_fg_bg = style.hzd_fg_bg
local dis_colors = style.dis_colors
local gry_wht = style.gray_white local gry_wht = style.gray_white
local ind_grn = style.ind_grn
local ind_yel = style.ind_yel
local ind_red = style.ind_red
local ind_wht = style.ind_wht
local period = core.flasher.PERIOD local period = core.flasher.PERIOD
-- create a unit view -- create a unit view
---@param parent graphics_element parent ---@param parent graphics_element parent
---@param id integer ---@param id integer
local function init(parent, id) local function init(parent, id)
local unit = iocontrol.get_db().units[id] ---@type ioctl_unit local s_hi_box = style.theme.highlight_box
local f_ps = iocontrol.get_db().facility.ps local s_hi_bright = style.theme.highlight_box_bright
local s_field = style.theme.field_box
local hc_text = style.hc_text
local lu_cpair = style.lu_colors
local hzd_fg_bg = style.hzd_fg_bg
local dis_colors = style.dis_colors
local arrow_fg_bg = cpair(style.theme.label, s_hi_box.bkg)
local ind_bkg = style.ind_bkg
local ind_grn = style.ind_grn
local ind_yel = style.ind_yel
local ind_red = style.ind_red
local ind_wht = style.ind_wht
local db = iocontrol.get_db()
local unit = db.units[id] ---@type ioctl_unit
local f_ps = db.facility.ps
local main = Div{parent=parent,x=1,y=1} local main = Div{parent=parent,x=1,y=1}
@@ -62,7 +71,7 @@ local function init(parent, id)
local b_ps = unit.boiler_ps_tbl local b_ps = unit.boiler_ps_tbl
local t_ps = unit.turbine_ps_tbl local t_ps = unit.turbine_ps_tbl
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=ALIGN.CENTER,height=1,fg_bg=style.header} TextBox{parent=main,text="Reactor Unit #" .. id,alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
----------------------------- -----------------------------
-- main stats and core map -- -- main stats and core map --
@@ -73,11 +82,11 @@ local function init(parent, id)
core_map.register(u_ps, "size", function (s) core_map.resize(s[1], s[2]) end) core_map.register(u_ps, "size", function (s) core_map.resize(s[1], s[2]) end)
TextBox{parent=main,x=12,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label} TextBox{parent=main,x=12,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label}
local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=s_field}
heating_r.register(u_ps, "heating_rate", heating_r.update) heating_r.register(u_ps, "heating_rate", heating_r.update)
TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label} TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label}
local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=s_field}
burn_r.register(u_ps, "burn_rate", burn_r.update) burn_r.register(u_ps, "burn_rate", burn_r.update)
TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label} TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label}
@@ -87,7 +96,7 @@ local function init(parent, id)
TextBox{parent=main,text="H",x=8,y=22,width=1,height=1,fg_bg=style.label} TextBox{parent=main,text="H",x=8,y=22,width=1,height=1,fg_bg=style.label}
TextBox{parent=main,text="W",x=10,y=22,width=1,height=1,fg_bg=style.label} TextBox{parent=main,text="W",x=10,y=22,width=1,height=1,fg_bg=style.label}
local fuel = VerticalBar{parent=main,x=2,y=23,fg_bg=cpair(colors.black,colors.gray),height=4,width=1} local fuel = VerticalBar{parent=main,x=2,y=23,fg_bg=cpair(style.theme.fuel_color,colors.gray),height=4,width=1}
local ccool = VerticalBar{parent=main,x=4,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1} local ccool = VerticalBar{parent=main,x=4,y=23,fg_bg=cpair(colors.blue,colors.gray),height=4,width=1}
local hcool = VerticalBar{parent=main,x=8,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local hcool = VerticalBar{parent=main,x=8,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1}
local waste = VerticalBar{parent=main,x=10,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1} local waste = VerticalBar{parent=main,x=10,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1}
@@ -114,19 +123,20 @@ local function init(parent, id)
end) end)
TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label} TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label}
local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} local fmt = util.trinary(string.len(db.temp_label) == 2, "%10.2f", "%11.2f")
core_temp.register(u_ps, "temp", core_temp.update) local core_temp = DataIndicator{parent=main,x=32,label="",format=fmt,value=0,commas=true,unit=db.temp_label,lu_colors=lu_cpair,width=13,fg_bg=s_field}
core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label} TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label}
local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=s_field}
act_burn_r.register(u_ps, "act_burn_rate", act_burn_r.update) act_burn_r.register(u_ps, "act_burn_rate", act_burn_r.update)
TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label} TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label}
local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=s_field}
damage_p.register(u_ps, "damage", damage_p.update) damage_p.register(u_ps, "damage", damage_p.update)
TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label} TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label}
local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=s_field}
radiation.register(u_ps, "radiation", radiation.update) radiation.register(u_ps, "radiation", radiation.update)
------------------- -------------------
@@ -149,9 +159,9 @@ local function init(parent, id)
local annunciator = Div{parent=main,width=23,height=18,x=22,y=3} local annunciator = Div{parent=main,width=23,height=18,x=22,y=3}
-- connectivity -- connectivity
local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(ind_grn.fgd,ind_red.fgd)}
local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=ind_wht} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=ind_wht}
local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd}
plc_online.register(u_ps, "PLCOnline", plc_online.update) plc_online.register(u_ps, "PLCOnline", plc_online.update)
plc_hbeat.register(u_ps, "PLCHeartbeat", plc_hbeat.update) plc_hbeat.register(u_ps, "PLCHeartbeat", plc_hbeat.update)
@@ -173,7 +183,7 @@ local function init(parent, id)
local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=ind_yel} local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=ind_yel}
local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=ind_red} local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=ind_red}
local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=ind_yel} local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=ind_yel}
local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=ind_yel} local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=ind_yel}
local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=ind_red} local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=ind_red}
local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=ind_yel} local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=ind_yel}
local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=ind_yel} local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=ind_yel}
@@ -208,7 +218,7 @@ local function init(parent, id)
local rps_loc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=ind_yel} local rps_loc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=ind_yel}
local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS} local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=ind_yel,flash=true,period=period.BLINK_500_MS} local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=ind_red,flash=true,period=period.BLINK_500_MS}
rps_trp.register(u_ps, "rps_tripped", rps_trp.update) rps_trp.register(u_ps, "rps_tripped", rps_trp.update)
rps_dmg.register(u_ps, "high_dmg", rps_dmg.update) rps_dmg.register(u_ps, "high_dmg", rps_dmg.update)
@@ -229,7 +239,7 @@ local function init(parent, id)
local rcs_tags = Div{parent=rcs,width=2,height=16,x=1,y=7} local rcs_tags = Div{parent=rcs,width=2,height=16,x=1,y=7}
local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=ind_yel} local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=ind_yel}
local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.green} local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=ind_bkg,c2=ind_wht.fgd,c3=ind_grn.fgd}
local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=ind_yel} local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=ind_yel}
local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=ind_yel} local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=ind_yel}
local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=ind_yel} local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=ind_yel}
@@ -252,14 +262,14 @@ local function init(parent, id)
-- boiler annunciator panel(s) -- boiler annunciator panel(s)
if available_space > 0 then _add_space() end
if unit.num_boilers > 0 then if unit.num_boilers > 0 then
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg} if available_space > 0 then _add_space() end
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=hc_text}
local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red} local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
b1_wll.register(b_ps[1], "WaterLevelLow", b1_wll.update) b1_wll.register(b_ps[1], "WaterLevelLow", b1_wll.update)
TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=hc_text}
local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel} local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
b1_hr.register(b_ps[1], "HeatingRateLow", b1_hr.update) b1_hr.register(b_ps[1], "HeatingRateLow", b1_hr.update)
end end
@@ -271,11 +281,11 @@ local function init(parent, id)
_add_space() _add_space()
end end
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=hc_text}
local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red} local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
b2_wll.register(b_ps[2], "WaterLevelLow", b2_wll.update) b2_wll.register(b_ps[2], "WaterLevelLow", b2_wll.update)
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=hc_text}
local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel} local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
b2_hr.register(b_ps[2], "HeatingRateLow", b2_hr.update) b2_hr.register(b_ps[2], "HeatingRateLow", b2_hr.update)
end end
@@ -284,19 +294,19 @@ local function init(parent, id)
if available_space > 1 then _add_space() end if available_space > 1 then _add_space() end
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
t1_sdo.register(t_ps[1], "SteamDumpOpen", t1_sdo.update) t1_sdo.register(t_ps[1], "SteamDumpOpen", t1_sdo.update)
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red} local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
t1_tos.register(t_ps[1], "TurbineOverSpeed", t1_tos.update) t1_tos.register(t_ps[1], "TurbineOverSpeed", t1_tos.update)
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS} local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
t1_gtrp.register(t_ps[1], "GeneratorTrip", t1_gtrp.update) t1_gtrp.register(t_ps[1], "GeneratorTrip", t1_gtrp.update)
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=hc_text}
local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS} local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
t1_trp.register(t_ps[1], "TurbineTrip", t1_trp.update) t1_trp.register(t_ps[1], "TurbineTrip", t1_trp.update)
@@ -305,19 +315,19 @@ local function init(parent, id)
_add_space() _add_space()
end end
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
t2_sdo.register(t_ps[2], "SteamDumpOpen", t2_sdo.update) t2_sdo.register(t_ps[2], "SteamDumpOpen", t2_sdo.update)
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red} local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
t2_tos.register(t_ps[2], "TurbineOverSpeed", t2_tos.update) t2_tos.register(t_ps[2], "TurbineOverSpeed", t2_tos.update)
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS} local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
t2_gtrp.register(t_ps[2], "GeneratorTrip", t2_gtrp.update) t2_gtrp.register(t_ps[2], "GeneratorTrip", t2_gtrp.update)
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=hc_text}
local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS} local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
t2_trp.register(t_ps[2], "TurbineTrip", t2_trp.update) t2_trp.register(t_ps[2], "TurbineTrip", t2_trp.update)
end end
@@ -325,30 +335,32 @@ local function init(parent, id)
if unit.num_turbines > 2 then if unit.num_turbines > 2 then
if available_space > 3 then _add_space() end if available_space > 3 then _add_space() end
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_red.fgd}
t3_sdo.register(t_ps[3], "SteamDumpOpen", t3_sdo.update) t3_sdo.register(t_ps[3], "SteamDumpOpen", t3_sdo.update)
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red} local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
t3_tos.register(t_ps[3], "TurbineOverSpeed", t3_tos.update) t3_tos.register(t_ps[3], "TurbineOverSpeed", t3_tos.update)
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS} local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
t3_gtrp.register(t_ps[3], "GeneratorTrip", t3_gtrp.update) t3_gtrp.register(t_ps[3], "GeneratorTrip", t3_gtrp.update)
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=hc_text}
local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS} local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update) t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update)
end end
util.nop()
---------------------- ----------------------
-- reactor controls -- -- reactor controls --
---------------------- ----------------------
local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=gry_wht} local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=s_hi_box}
local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=gry_wht,fg_bg=bw_fg_bg} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=arrow_fg_bg,arrow_disable=style.theme.disabled}
TextBox{parent=burn_control,x=9,y=2,text="mB/t"} TextBox{parent=burn_control,x=9,y=2,text="mB/t",fg_bg=style.theme.label_fg}
local set_burn = function () unit.set_burn(burn_rate.get_value()) end local set_burn = function () unit.set_burn(burn_rate.get_value()) end
local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=style.wh_gray,dis_fg_bg=dis_colors,callback=set_burn} local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=style.wh_gray,dis_fg_bg=dis_colors,callback=set_burn}
@@ -394,22 +406,22 @@ local function init(parent, id)
-- alarm management -- -- alarm management --
---------------------- ----------------------
local alarm_panel = Div{parent=main,x=2,y=36,width=29,height=16,fg_bg=bw_fg_bg} local alarm_panel = Div{parent=main,x=2,y=36,width=29,height=16,fg_bg=s_hi_bright}
local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
local a_dmg = AlarmLight{parent=alarm_panel,x=6,label="Critical Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_dmg = AlarmLight{parent=alarm_panel,x=6,label="Critical Damage",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
alarm_panel.line_break() alarm_panel.line_break()
local a_rcl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Lost",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rcl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Lost",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
local a_rcd = AlarmLight{parent=alarm_panel,x=6,label="Reactor Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rcd = AlarmLight{parent=alarm_panel,x=6,label="Reactor Damage",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
local a_rot = AlarmLight{parent=alarm_panel,x=6,label="Reactor Over Temp",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rot = AlarmLight{parent=alarm_panel,x=6,label="Reactor Over Temp",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
local a_rht = AlarmLight{parent=alarm_panel,x=6,label="Reactor High Temp",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} local a_rht = AlarmLight{parent=alarm_panel,x=6,label="Reactor High Temp",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
local a_rwl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste Leak",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_rwl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste Leak",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
local a_rwh = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste High",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} local a_rwh = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste High",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
alarm_panel.line_break() alarm_panel.line_break()
local a_rps = AlarmLight{parent=alarm_panel,x=6,label="RPS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} local a_rps = AlarmLight{parent=alarm_panel,x=6,label="RPS Transient",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_500_MS}
local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=ind_bkg,c2=ind_red.fgd,c3=ind_grn.fgd,flash=true,period=period.BLINK_250_MS}
a_brc.register(u_ps, "Alarm_1", a_brc.update) a_brc.register(u_ps, "Alarm_1", a_brc.update)
a_rad.register(u_ps, "Alarm_2", a_rad.update) a_rad.register(u_ps, "Alarm_2", a_rad.update)
@@ -462,9 +474,9 @@ local function init(parent, id)
-- color tags -- color tags
TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.cyan)} TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.cyan)}
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.blue)} TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white,colors.blue)} TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(s_hi_bright.bkg,colors.blue)}
-------------------------------- --------------------------------
-- automatic control settings -- -- automatic control settings --
@@ -476,7 +488,7 @@ local function init(parent, id)
local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" }
local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.gray,colors.white),select_color=colors.purple} local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple}
group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end) group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end)
@@ -488,7 +500,7 @@ local function init(parent, id)
auto_div.line_break() auto_div.line_break()
TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label} TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label}
local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg} local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=s_field}
auto_grp.register(u_ps, "auto_group", auto_grp.set_value) auto_grp.register(u_ps, "auto_group", auto_grp.set_value)

View File

@@ -24,17 +24,12 @@ local ALIGN = core.ALIGN
local sprintf = util.sprintf local sprintf = util.sprintf
local border = core.border local border = core.border
local cpair = core.cpair
local pipe = core.pipe local pipe = core.pipe
local wh_gray = style.wh_gray local wh_gray = style.wh_gray
local bw_fg_bg = style.bw_fg_bg
local text_c = style.text_colors
local lu_c = style.lu_colors
local lg_gray = style.lg_gray local lg_gray = style.lg_gray
local ind_grn = style.ind_grn
local ind_wht = style.ind_wht
-- make a new unit flow window -- make a new unit flow window
---@param parent graphics_element parent ---@param parent graphics_element parent
---@param x integer top left x ---@param x integer top left x
@@ -42,6 +37,15 @@ local ind_wht = style.ind_wht
---@param wide boolean whether to render wide version ---@param wide boolean whether to render wide version
---@param unit ioctl_unit unit database entry ---@param unit ioctl_unit unit database entry
local function make(parent, x, y, wide, unit) local function make(parent, x, y, wide, unit)
local s_field = style.theme.field_box
local text_c = style.text_colors
local lu_c = style.lu_colors
local lu_c_d = style.lu_colors_dark
local ind_grn = style.ind_grn
local ind_wht = style.ind_wht
local height = 16 local height = 16
local v_start = 1 + ((unit.unit_id - 1) * 5) local v_start = 1 + ((unit.unit_id - 1) * 5)
@@ -99,35 +103,35 @@ local function make(parent, x, y, wide, unit)
table.insert(rc_pipes, pipe(_wide(92, 78), py, _wide(104, 83), py, colors.white, true)) table.insert(rc_pipes, pipe(_wide(92, 78), py, _wide(104, 83), py, colors.white, true))
end end
PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=colors.lightGray} PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=style.theme.bg}
if unit.num_boilers > 0 then if unit.num_boilers > 0 then
local cc_rate = DataIndicator{parent=root,x=_wide(25,22),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg} local cc_rate = DataIndicator{parent=root,x=_wide(25,22),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
local hc_rate = DataIndicator{parent=root,x=_wide(25,22),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg} local hc_rate = DataIndicator{parent=root,x=_wide(25,22),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
cc_rate.register(unit.unit_ps, "boiler_boil_sum", function (sum) cc_rate.update(sum * 10) end) cc_rate.register(unit.unit_ps, "boiler_boil_sum", function (sum) cc_rate.update(sum * 10) end)
hc_rate.register(unit.unit_ps, "heating_rate", hc_rate.update) hc_rate.register(unit.unit_ps, "heating_rate", hc_rate.update)
local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray} local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=ALIGN.CENTER,height=1} TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=ALIGN.CENTER,height=1}
TextBox{parent=boiler,y=3,text=util.trinary(unit.num_boilers>1,"BOILERS","BOILER"),alignment=ALIGN.CENTER,height=1} TextBox{parent=boiler,y=3,text=util.trinary(unit.num_boilers>1,"BOILERS","BOILER"),alignment=ALIGN.CENTER,height=1}
TextBox{parent=root,x=_wide(47,40),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} TextBox{parent=root,x=_wide(47,40),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
TextBox{parent=root,x=_wide(65,58),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} TextBox{parent=root,x=_wide(65,58),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
local wt_rate = DataIndicator{parent=root,x=_wide(71,61),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg} local wt_rate = DataIndicator{parent=root,x=_wide(71,61),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
local st_rate = DataIndicator{parent=root,x=_wide(71,61),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg} local st_rate = DataIndicator{parent=root,x=_wide(71,61),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update) wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update)
st_rate.register(unit.unit_ps, "boiler_boil_sum", st_rate.update) st_rate.register(unit.unit_ps, "boiler_boil_sum", st_rate.update)
else else
local wt_rate = DataIndicator{parent=root,x=28,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg} local wt_rate = DataIndicator{parent=root,x=28,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
local st_rate = DataIndicator{parent=root,x=28,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg} local st_rate = DataIndicator{parent=root,x=28,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=s_field}
wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update) wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update)
st_rate.register(unit.unit_ps, "heating_rate", st_rate.update) st_rate.register(unit.unit_ps, "heating_rate", st_rate.update)
end end
local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray} local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=ALIGN.CENTER,height=1} TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=ALIGN.CENTER,height=1}
TextBox{parent=turbine,y=3,text=util.trinary(unit.num_turbines>1,"GENERATORS","GENERATOR"),alignment=ALIGN.CENTER,height=1} TextBox{parent=turbine,y=3,text=util.trinary(unit.num_turbines>1,"GENERATORS","GENERATOR"),alignment=ALIGN.CENTER,height=1}
TextBox{parent=root,x=_wide(93,79),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} TextBox{parent=root,x=_wide(93,79),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
@@ -135,7 +139,7 @@ local function make(parent, x, y, wide, unit)
for i = 1, unit.num_turbines do for i = 1, unit.num_turbines do
local ry = 1 + (2 * (i - 1)) + prv_yo local ry = 1 + (2 * (i - 1)) + prv_yo
TextBox{parent=root,x=_wide(125,103),y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3,height=1} TextBox{parent=root,x=_wide(125,103),y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3,height=1}
local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=colors.gray,c2=colors.yellow,c3=colors.red} local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=style.ind_bkg,c2=style.ind_yel.fgd,c3=style.ind_red.fgd}
state.register(unit.turbine_ps_tbl[i], "SteamDumpOpen", state.update) state.register(unit.turbine_ps_tbl[i], "SteamDumpOpen", state.update)
end end
@@ -145,6 +149,8 @@ local function make(parent, x, y, wide, unit)
local waste = Div{parent=root,x=3,y=6} local waste = Div{parent=root,x=3,y=6}
local waste_c = style.theme.fuel_color
local waste_pipes = { local waste_pipes = {
pipe(0, 0, _wide(19, 16), 1, colors.brown, true), pipe(0, 0, _wide(19, 16), 1, colors.brown, true),
pipe(_wide(14, 13), 1, _wide(19, 17), 5, colors.brown, true), pipe(_wide(14, 13), 1, _wide(19, 17), 5, colors.brown, true),
@@ -158,12 +164,12 @@ local function make(parent, x, y, wide, unit)
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true), pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true),
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true), pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true),
pipe(_wide(108, 94), 1, _wide(132, 110), 6, colors.black, true, true), pipe(_wide(108, 94), 1, _wide(132, 110), 6, waste_c, true, true),
pipe(_wide(108, 94), 4, _wide(111, 95), 1, colors.black, true, true), pipe(_wide(108, 94), 4, _wide(111, 95), 1, waste_c, true, true),
pipe(_wide(132, 110), 6, _wide(130, 108), 6, colors.black, true, true) pipe(_wide(132, 110), 6, _wide(130, 108), 6, waste_c, true, true)
} }
PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=colors.lightGray} PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=style.theme.bg}
local function _valve(vx, vy, n) local function _valve(vx, vy, n)
TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=text_c,width=2,height=1} TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=text_c,width=2,height=1}
@@ -175,16 +181,16 @@ local function make(parent, x, y, wide, unit)
local function _machine(mx, my, name) local function _machine(mx, my, name)
local l = string.len(name) + 2 local l = string.len(name) + 2
TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=ALIGN.CENTER,fg_bg=lg_gray,width=l,height=1} TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=ALIGN.CENTER,fg_bg=cpair(style.theme.bg,style.theme.header.bkg),width=l,height=1}
TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=ALIGN.CENTER,fg_bg=wh_gray,width=l,height=1} TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=ALIGN.CENTER,fg_bg=style.theme.header,width=l,height=1}
end end
local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=bw_fg_bg} local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
local pu_rate = DataIndicator{parent=waste,x=_wide(82,70),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg} local pu_rate = DataIndicator{parent=waste,x=_wide(82,70),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=s_field}
local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg} local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg} local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg} local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field}
local spent_rate = DataIndicator{parent=waste,x=_wide(117,99),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg} local spent_rate = DataIndicator{parent=waste,x=_wide(117,98),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%8.3f",value=0,width=13,fg_bg=s_field}
waste_rate.register(unit.unit_ps, "act_burn_rate", waste_rate.update) waste_rate.register(unit.unit_ps, "act_burn_rate", waste_rate.update)
pu_rate.register(unit.unit_ps, "pu_rate", pu_rate.update) pu_rate.register(unit.unit_ps, "pu_rate", pu_rate.update)
@@ -204,17 +210,17 @@ local function make(parent, x, y, wide, unit)
_machine(_wide(116, 94), 6, "SPENT WASTE \x1b") _machine(_wide(116, 94), 6, "SPENT WASTE \x1b")
TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray} TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=bw_fg_bg} local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=style.theme.highlight_box_bright}
local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn} local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn}
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c,label="CNT",unit="",format="%2d",value=0,width=7} local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c_d,label="CNT",unit="",format="%2d",value=0,width=7}
local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17} local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c_d,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17}
local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17} local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17}
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c,label="IN",unit="mB/t",format="%9.2f",value=0,width=17} local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="IN",unit="mB/t",format="%9.2f",value=0,width=17}
sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end) sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end)
sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update) sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update)
sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update) sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update)
sna_max.register(unit.unit_ps, "sna_prod_rate", sna_max.update) sna_max.register(unit.unit_ps, "sna_max_rate", sna_max.update)
sna_in.register(unit.unit_ps, "sna_in", sna_in.update) sna_in.register(unit.unit_ps, "sna_in", sna_in.update)
return root return root

View File

@@ -44,7 +44,7 @@ local function make(parent, x, y, unit)
local root = Div{parent=parent,x=x,y=y,width=80,height=height} local root = Div{parent=parent,x=x,y=y,width=80,height=height}
-- unit header message -- unit header message
TextBox{parent=root,text="Unit #"..unit.unit_id,alignment=ALIGN.CENTER,height=1,fg_bg=style.header} TextBox{parent=root,text="Unit #"..unit.unit_id,alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
------------- -------------
-- REACTOR -- -- REACTOR --
@@ -66,7 +66,7 @@ local function make(parent, x, y, unit)
table.insert(coolant_pipes, pipe(2, 0, 11, 11, colors.orange)) table.insert(coolant_pipes, pipe(2, 0, 11, 11, colors.orange))
end end
PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=colors.lightGray} PipeNetwork{parent=root,x=4,y=10,pipes=coolant_pipes,bg=style.theme.bg}
end end
------------- -------------
@@ -164,10 +164,10 @@ local function make(parent, x, y, unit)
table.insert(steam_pipes_b, pipe(0, 18, 2, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction table.insert(steam_pipes_b, pipe(0, 18, 2, 18, colors.blue, false, true)) -- water boiler 2 to turbine 2 junction
end end
PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=colors.lightGray} PipeNetwork{parent=root,x=47,y=11,pipes=steam_pipes_a,bg=style.theme.bg}
end end
PipeNetwork{parent=root,x=54,y=3,pipes=steam_pipes_b,bg=colors.lightGray} PipeNetwork{parent=root,x=54,y=3,pipes=steam_pipes_b,bg=style.theme.bg}
return root return root
end end

View File

@@ -32,13 +32,16 @@ local border = core.border
local pipe = core.pipe local pipe = core.pipe
local wh_gray = style.wh_gray local wh_gray = style.wh_gray
local bw_fg_bg = style.bw_fg_bg
local text_col = style.text_colors
local lu_col = style.lu_colors
-- create new flow view -- create new flow view
---@param main graphics_element main displaybox ---@param main graphics_element main displaybox
local function init(main) local function init(main)
local s_hi_bright = style.theme.highlight_box_bright
local s_field = style.theme.field_box
local text_col = style.text_colors
local lu_col = style.lu_colors
local lu_c_d = style.lu_colors_dark
local facility = iocontrol.get_db().facility local facility = iocontrol.get_db().facility
local units = iocontrol.get_db().units local units = iocontrol.get_db().units
@@ -46,9 +49,9 @@ local function init(main)
local tank_list = facility.tank_list local tank_list = facility.tank_list
-- window header message -- window header message
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
-- max length example: "01:23:45 AM - Wednesday, September 28 2022" -- max length example: "01:23:45 AM - Wednesday, September 28 2022"
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=style.header} local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=style.theme.header}
datetime.register(facility.ps, "date_time", datetime.set_value) datetime.register(facility.ps, "date_time", datetime.set_value)
@@ -240,16 +243,17 @@ local function init(main)
local flow_x = 3 local flow_x = 3
if #water_pipes > 0 then if #water_pipes > 0 then
flow_x = 25 flow_x = 25
PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=colors.lightGray} PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=style.theme.bg}
end end
for i = 1, facility.num_units do for i = 1, facility.num_units do
local y_offset = y_ofs(i) local y_offset = y_ofs(i)
unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i]) unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i])
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true)) table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true))
util.nop()
end end
PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=colors.lightGray} PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=style.theme.bg}
----------------- -----------------
-- tank valves -- -- tank valves --
@@ -297,7 +301,7 @@ local function init(main)
TextBox{parent=tank_box,x=2,y=3,text="Fill",height=1,width=10,fg_bg=style.label} TextBox{parent=tank_box,x=2,y=3,text="Fill",height=1,width=10,fg_bg=style.label}
local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col} local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col}
local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=16,fg_bg=bw_fg_bg} local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=16,fg_bg=s_field}
TextBox{parent=tank_box,x=2,y=6,text="Water Level",height=1,width=11,fg_bg=style.label} TextBox{parent=tank_box,x=2,y=6,text="Water Level",height=1,width=11,fg_bg=style.label}
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16} local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16}
@@ -332,6 +336,8 @@ local function init(main)
end end
end end
util.nop()
--------- ---------
-- SPS -- -- SPS --
--------- ---------
@@ -348,12 +354,12 @@ local function init(main)
status.register(facility.sps_ps_tbl[1], "computed_status", status.update) status.register(facility.sps_ps_tbl[1], "computed_status", status.update)
TextBox{parent=sps_box,x=2,y=3,text="Input Rate",height=1,width=10,fg_bg=style.label} TextBox{parent=sps_box,x=2,y=3,text="Input Rate",height=1,width=10,fg_bg=style.label}
local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.3f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg} local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=s_field}
sps_in.register(facility.ps, "po_am_rate", sps_in.update) sps_in.register(facility.ps, "po_am_rate", sps_in.update)
TextBox{parent=sps_box,x=2,y=6,text="Production Rate",height=1,width=15,fg_bg=style.label} TextBox{parent=sps_box,x=2,y=6,text="Production Rate",height=1,width=15,fg_bg=style.label}
local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg} local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=s_field}
sps_rate.register(facility.sps_ps_tbl[1], "process_rate", function (r) sps_rate.update(r * 1000) end) sps_rate.register(facility.sps_ps_tbl[1], "process_rate", function (r) sps_rate.update(r * 1000) end)
@@ -362,24 +368,24 @@ local function init(main)
---------------- ----------------
TextBox{parent=main,x=145,y=16,text="RAW WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray} TextBox{parent=main,x=145,y=16,text="RAW WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
local raw_waste = Rectangle{parent=main,x=145,y=17,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=bw_fg_bg} local raw_waste = Rectangle{parent=main,x=145,y=17,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
local sum_raw_waste = DataIndicator{parent=raw_waste,lu_colors=lu_col,label="SUM",unit="mB/t",format="%8.2f",value=0,width=17} local sum_raw_waste = DataIndicator{parent=raw_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.2f",value=0,width=17}
sum_raw_waste.register(facility.ps, "burn_sum", sum_raw_waste.update) sum_raw_waste.register(facility.ps, "burn_sum", sum_raw_waste.update)
TextBox{parent=main,x=145,y=21,text="PROC. WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray} TextBox{parent=main,x=145,y=21,text="PROC. WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
local pr_waste = Rectangle{parent=main,x=145,y=22,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=bw_fg_bg} local pr_waste = Rectangle{parent=main,x=145,y=22,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=s_hi_bright}
local pu = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17} local pu = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17}
local po = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Po",unit="mB/t",format="%9.3f",value=0,width=17} local po = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Po",unit="mB/t",format="%9.2f",value=0,width=17}
local popl = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="PoPl",unit="mB/t",format="%7.3f",value=0,width=17} local popl = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="PoPl",unit="mB/t",format="%7.2f",value=0,width=17}
pu.register(facility.ps, "pu_rate", pu.update) pu.register(facility.ps, "pu_rate", pu.update)
po.register(facility.ps, "po_rate", po.update) po.register(facility.ps, "po_rate", po.update)
popl.register(facility.ps, "po_pl_rate", popl.update) popl.register(facility.ps, "po_pl_rate", popl.update)
TextBox{parent=main,x=145,y=28,text="SPENT WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray} TextBox{parent=main,x=145,y=28,text="SPENT WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
local sp_waste = Rectangle{parent=main,x=145,y=29,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=bw_fg_bg} local sp_waste = Rectangle{parent=main,x=145,y=29,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=s_hi_bright}
local sum_sp_waste = DataIndicator{parent=sp_waste,lu_colors=lu_col,label="SUM",unit="mB/t",format="%8.3f",value=0,width=17} local sum_sp_waste = DataIndicator{parent=sp_waste,lu_colors=lu_c_d,label="SUM",unit="mB/t",format="%8.3f",value=0,width=17}
sum_sp_waste.register(facility.ps, "spent_waste_rate", sum_sp_waste.update) sum_sp_waste.register(facility.ps, "spent_waste_rate", sum_sp_waste.update)
end end

View File

@@ -22,8 +22,11 @@ local TextBox = require("graphics.elements.textbox")
local TabBar = require("graphics.elements.controls.tabbar") local TabBar = require("graphics.elements.controls.tabbar")
local LED = require("graphics.elements.indicators.led") local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb") local RGBLED = require("graphics.elements.indicators.ledrgb")
local LINK_STATE = types.PANEL_LINK_STATE
local ALIGN = core.ALIGN local ALIGN = core.ALIGN
local cpair = core.cpair local cpair = core.cpair
@@ -36,7 +39,7 @@ local led_grn = style.led_grn
local function init(panel, num_units) local function init(panel, num_units)
local ps = iocontrol.get_db().fp.ps local ps = iocontrol.get_db().fp.ps
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.fp.header} TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.fp_theme.header}
local page_div = Div{parent=panel,x=1,y=3} local page_div = Div{parent=panel,x=1,y=3}
@@ -56,19 +59,58 @@ local function init(panel, num_units)
heartbeat.register(ps, "heartbeat", heartbeat.update) heartbeat.register(ps, "heartbeat", heartbeat.update)
local modem = LED{parent=system,label="MODEM",colors=led_grn} local modem = LED{parent=system,label="MODEM",colors=led_grn}
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
network.update(types.PANEL_LINK_STATE.DISCONNECTED) if not style.colorblind then
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.fp_ind_bkg}}
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
network.register(ps, "link_state", network.update)
else
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green}
nt_lnk.register(ps, "link_state", function (state)
local value = 2
if state == LINK_STATE.DISCONNECTED then
value = 1
elseif state == LINK_STATE.LINKED then
value = 3
end
nt_lnk.update(value)
end)
nt_ver.register(ps, "link_state", function (state)
local value = 3
if state == LINK_STATE.BAD_VERSION then
value = 2
elseif state == LINK_STATE.DISCONNECTED then
value = 1
end
nt_ver.update(value)
end)
end
system.line_break() system.line_break()
modem.register(ps, "has_modem", modem.update) modem.register(ps, "has_modem", modem.update)
network.register(ps, "link_state", network.update)
local speaker = LED{parent=system,label="SPEAKER",colors=led_grn} local speaker = LED{parent=system,label="SPEAKER",colors=led_grn}
speaker.register(ps, "has_speaker", speaker.update) speaker.register(ps, "has_speaker", speaker.update)
system.line_break()
local rt_main = LED{parent=system,label="RT MAIN",colors=led_grn}
local rt_render = LED{parent=system,label="RT RENDER",colors=led_grn}
rt_main.register(ps, "routine__main", rt_main.update)
rt_render.register(ps, "routine__render", rt_render.update)
---@diagnostic disable-next-line: undefined-field ---@diagnostic disable-next-line: undefined-field
local comp_id = util.sprintf("(%d)", os.getComputerID()) local comp_id = util.sprintf("(%d)", os.getComputerID())
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp_label} TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp.disabled_fg}
local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2} local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2}
@@ -89,7 +131,7 @@ local function init(panel, num_units)
-- about footer -- about footer
-- --
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp_label} local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1} local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1} local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
@@ -103,7 +145,7 @@ local function init(panel, num_units)
-- API page -- API page
local api_page = Div{parent=page_div,x=1,y=1,hidden=true} local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp_text,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
local _ = Div{parent=api_list,height=1,hidden=true} -- padding local _ = Div{parent=api_list,height=1,hidden=true} -- padding
-- assemble page panes -- assemble page panes
@@ -113,11 +155,11 @@ local function init(panel, num_units)
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
local tabs = { local tabs = {
{ name = "CRD", color = style.fp_text }, { name = "CRD", color = style.fp.text },
{ name = "API", color = style.fp_text }, { name = "API", color = style.fp.text },
} }
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=style.bw_fg_bg} TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=style.fp_theme.highlight_box_bright}
-- link pocket API list management to PGI -- link pocket API list management to PGI
pgi.link_elements(api_list, pkt_entry) pgi.link_elements(api_list, pkt_entry)

View File

@@ -2,6 +2,8 @@
-- Main SCADA Coordinator GUI -- Main SCADA Coordinator GUI
-- --
local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol") local iocontrol = require("coordinator.iocontrol")
local style = require("coordinator.ui.style") local style = require("coordinator.ui.style")
@@ -21,14 +23,16 @@ local ALIGN = core.ALIGN
-- create new main view -- create new main view
---@param main graphics_element main displaybox ---@param main graphics_element main displaybox
local function init(main) local function init(main)
local s_header = style.theme.header
local facility = iocontrol.get_db().facility local facility = iocontrol.get_db().facility
local units = iocontrol.get_db().units local units = iocontrol.get_db().units
-- window header message -- window header message
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=ALIGN.CENTER,height=1,fg_bg=s_header}
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=style.lg_white,width=12,fg_bg=style.header} local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=style.lg_white,width=12,fg_bg=s_header}
-- max length example: "01:23:45 AM - Wednesday, September 28 2022" -- max length example: "01:23:45 AM - Wednesday, September 28 2022"
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=style.header} local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=ALIGN.RIGHT,width=42,height=1,fg_bg=s_header}
ping.register(facility.ps, "sv_ping", ping.update) ping.register(facility.ps, "sv_ping", ping.update)
datetime.register(facility.ps, "date_time", datetime.set_value) datetime.register(facility.ps, "date_time", datetime.set_value)
@@ -51,6 +55,8 @@ local function init(main)
cnc_y_start = cnc_y_start + row_1_height + 1 cnc_y_start = cnc_y_start + row_1_height + 1
util.nop()
if facility.num_units >= 3 then if facility.num_units >= 3 then
-- base offset 3, spacing 1, max height of units 1 and 2 -- base offset 3, spacing 1, max height of units 1 and 2
local row_2_offset = cnc_y_start local row_2_offset = cnc_y_start
@@ -62,12 +68,12 @@ local function init(main)
uo_4 = unit_overview(main, 84, row_2_offset, units[4]) uo_4 = unit_overview(main, 84, row_2_offset, units[4])
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1) cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1)
end end
util.nop()
end end
-- command & control -- command & control
cnc_y_start = cnc_y_start
-- induction matrix and process control interfaces are 24 tall + space needed for divider -- induction matrix and process control interfaces are 24 tall + space needed for divider
local cnc_bottom_align_start = main.get_height() - 26 local cnc_bottom_align_start = main.get_height() - 26
@@ -79,6 +85,8 @@ local function init(main)
process_ctl(main, 2, cnc_bottom_align_start) process_ctl(main, 2, cnc_bottom_align_start)
util.nop()
imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1])
end end

View File

@@ -2,79 +2,141 @@
-- Graphics Style Options -- Graphics Style Options
-- --
local core = require("graphics.core") local util = require("scada-common.util")
local core = require("graphics.core")
local themes = require("graphics.themes")
---@class crd_style
local style = {} local style = {}
local cpair = core.cpair local cpair = core.cpair
-- GLOBAL --
-- add color mappings for front panel
colors.ivory = colors.pink
colors.yellow_hc = colors.purple
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
-- front panel styling -- front panel styling
style.fp = {} style.fp_theme = themes.sandstone
style.fp = themes.get_fp_style(style.fp_theme)
style.fp.root = cpair(colors.black, colors.ivory) style.led_grn = cpair(colors.green, colors.green_off)
style.fp.header = cpair(colors.black, colors.lightGray)
style.fp.colors = {
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
-- { c = colors.white, hex = 0xdcd9ca },
{ c = colors.lightGray, hex = 0xb1b8b3 },
{ c = colors.gray, hex = 0x575757 },
-- { c = colors.black, hex = 0x191919 },
{ c = colors.brown, hex = 0x672223 } -- RED OFF
}
-- main GUI styling -- main GUI styling
style.root = cpair(colors.black, colors.lightGray) ---@class theme
style.header = cpair(colors.white, colors.gray) local smooth_stone = {
style.label = cpair(colors.gray, colors.lightGray) text = colors.black,
text_inv = colors.white,
label = colors.gray,
label_dark = colors.gray,
disabled = colors.lightGray,
bg = colors.lightGray,
checkbox_bg = colors.black,
accent_light = colors.white,
accent_dark = colors.gray,
style.colors = { fuel_color = colors.black,
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 }, header = cpair(colors.white, colors.gray),
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.lime, hex = 0x80ff80 }, text_fg = cpair(colors.black, colors._INHERIT),
{ c = colors.green, hex = 0x4aee8a }, label_fg = cpair(colors.gray, colors._INHERIT),
{ c = colors.cyan, hex = 0x34bac8 }, disabled_fg = cpair(colors.lightGray, colors._INHERIT),
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff }, highlight_box = cpair(colors.black, colors.white),
{ c = colors.purple, hex = 0xb156ee }, highlight_box_bright = cpair(colors.black, colors.white),
{ c = colors.pink, hex = 0xf26ba2 }, field_box = cpair(colors.black, colors.white),
{ c = colors.magenta, hex = 0xf9488a },
-- { c = colors.white, hex = 0xf0f0f0 }, colors = themes.smooth_stone.colors,
{ c = colors.lightGray, hex = 0xcacaca },
{ c = colors.gray, hex = 0x575757 }, -- color re-mappings for assistive modes
-- { c = colors.black, hex = 0x191919 }, color_modes = themes.smooth_stone.color_modes
-- { c = colors.brown, hex = 0x7f664c }
} }
---@type theme
local deepslate = {
text = colors.white,
text_inv = colors.black,
label = colors.lightGray,
label_dark = colors.gray,
disabled = colors.gray,
bg = colors.black,
checkbox_bg = colors.gray,
accent_light = colors.gray,
accent_dark = colors.lightGray,
fuel_color = colors.lightGray,
header = cpair(colors.white, colors.gray),
text_fg = cpair(colors.white, colors._INHERIT),
label_fg = cpair(colors.lightGray, colors._INHERIT),
disabled_fg = cpair(colors.gray, colors._INHERIT),
highlight_box = cpair(colors.white, colors.gray),
highlight_box_bright = cpair(colors.black, colors.lightGray),
field_box = cpair(colors.white, colors.gray),
colors = themes.deepslate.colors,
-- color re-mappings for assistive modes
color_modes = themes.deepslate.color_modes
}
style.theme = smooth_stone
-- set themes per configurations
---@param main UI_THEME main UI theme
---@param fp FP_THEME front panel theme
---@param color_mode COLOR_MODE the color mode to use
function style.set_themes(main, fp, color_mode)
local colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
local gray_ind_off = color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND
style.ind_bkg = colors.gray
style.fp_ind_bkg = util.trinary(gray_ind_off, colors.gray, colors.black)
style.ind_hi_box_bg = util.trinary(gray_ind_off, colors.gray, colors.black)
if main == themes.UI_THEME.SMOOTH_STONE then
style.theme = smooth_stone
style.ind_bkg = util.trinary(gray_ind_off, colors.gray, colors.black)
elseif main == themes.UI_THEME.DEEPSLATE then
style.theme = deepslate
style.ind_hi_box_bg = util.trinary(gray_ind_off, colors.lightGray, colors.black)
end
style.colorblind = colorblind
style.root = cpair(style.theme.text, style.theme.bg)
style.label = cpair(style.theme.label, style.theme.bg)
-- high contrast text (also tags)
style.hc_text = cpair(style.theme.text, style.theme.text_inv)
-- text on default background
style.text_colors = cpair(style.theme.text, style.theme.bg)
-- label & unit colors
style.lu_colors = cpair(style.theme.label, style.theme.label)
-- label & unit colors (darker if set)
style.lu_colors_dark = cpair(style.theme.label_dark, style.theme.label_dark)
style.ind_grn = cpair(util.trinary(colorblind, colors.blue, colors.green), style.ind_bkg)
style.ind_yel = cpair(colors.yellow, style.ind_bkg)
style.ind_red = cpair(colors.red, style.ind_bkg)
style.ind_wht = cpair(colors.white, style.ind_bkg)
if fp == themes.FP_THEME.SANDSTONE then
style.fp_theme = themes.sandstone
elseif fp == themes.FP_THEME.BASALT then
style.fp_theme = themes.basalt
end
style.fp = themes.get_fp_style(style.fp_theme)
end
-- COMMON COLOR PAIRS -- -- COMMON COLOR PAIRS --
style.wh_gray = cpair(colors.white, colors.gray) style.wh_gray = cpair(colors.white, colors.gray)
style.bw_fg_bg = cpair(colors.black, colors.white) style.bw_fg_bg = cpair(colors.black, colors.white)
style.text_colors = cpair(colors.black, colors.lightGray)
style.lu_colors = cpair(colors.gray, colors.gray)
style.hzd_fg_bg = style.wh_gray style.hzd_fg_bg = style.wh_gray
style.dis_colors = cpair(colors.white, colors.lightGray) style.dis_colors = cpair(colors.white, colors.lightGray)
@@ -82,15 +144,6 @@ style.lg_gray = cpair(colors.lightGray, colors.gray)
style.lg_white = cpair(colors.lightGray, colors.white) style.lg_white = cpair(colors.lightGray, colors.white)
style.gray_white = cpair(colors.gray, colors.white) style.gray_white = cpair(colors.gray, colors.white)
style.ind_grn = cpair(colors.green, colors.gray)
style.ind_yel = cpair(colors.yellow, colors.gray)
style.ind_red = cpair(colors.red, colors.gray)
style.ind_wht = style.wh_gray
style.fp_text = cpair(colors.black, colors.ivory)
style.fp_label = cpair(colors.lightGray, colors.ivory)
style.led_grn = cpair(colors.green, colors.green_off)
-- UI COMPONENTS -- -- UI COMPONENTS --
style.reactor = { style.reactor = {

View File

@@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
local core = {} local core = {}
core.version = "2.1.1" core.version = "2.2.4"
core.flasher = flasher core.flasher = flasher
core.events = events core.events = events
@@ -61,6 +61,9 @@ end
---@field blit_fgd string ---@field blit_fgd string
---@field blit_bkg string ---@field blit_bkg string
-- add inherited flag, 3 isn't a pure color so it wouldn't be used
colors._INHERIT = 3
-- create a new color pair definition -- create a new color pair definition
---@nodiscard ---@nodiscard
---@param a color ---@param a color

View File

@@ -49,9 +49,11 @@ local element = {}
---|indicator_light_args ---|indicator_light_args
---|power_indicator_args ---|power_indicator_args
---|rad_indicator_args ---|rad_indicator_args
---|signal_bar_args
---|state_indicator_args ---|state_indicator_args
---|tristate_indicator_light_args ---|tristate_indicator_light_args
---|vbar_args ---|vbar_args
---|app_multipane_args
---|colormap_args ---|colormap_args
---|displaybox_args ---|displaybox_args
---|div_args ---|div_args
@@ -196,6 +198,9 @@ function element.new(args, child_offset_x, child_offset_y)
---@param offset_y integer y offset for mouse events ---@param offset_y integer y offset for mouse events
---@param next_y integer next line if no y was provided ---@param next_y integer next line if no y was provided
function protected.prepare_template(offset_x, offset_y, next_y) function protected.prepare_template(offset_x, offset_y, next_y)
-- don't auto incrememnt y if inheriting height, that would cause an assertion
next_y = util.trinary(args.height == nil, 1, next_y)
-- record offsets in case there is a reposition -- record offsets in case there is a reposition
self.offset_x = offset_x self.offset_x = offset_x
self.offset_y = offset_y self.offset_y = offset_y
@@ -234,11 +239,24 @@ function element.new(args, child_offset_x, child_offset_y)
-- init colors -- init colors
if args.fg_bg ~= nil then if args.fg_bg ~= nil then
protected.fg_bg = args.fg_bg protected.fg_bg = core.cpair(args.fg_bg.fgd, args.fg_bg.bkg)
elseif args.parent ~= nil then
protected.fg_bg = args.parent.get_fg_bg()
end end
if args.parent ~= nil then
local p_fg_bg = args.parent.get_fg_bg()
if args.fg_bg == nil then
protected.fg_bg = core.cpair(p_fg_bg.fgd, p_fg_bg.bkg)
else
if protected.fg_bg.fgd == colors._INHERIT then protected.fg_bg = core.cpair(p_fg_bg.fgd, protected.fg_bg.bkg) end
if protected.fg_bg.bkg == colors._INHERIT then protected.fg_bg = core.cpair(protected.fg_bg.fgd, p_fg_bg.bkg) end
end
end
-- check colors
element.assert(protected.fg_bg.fgd ~= colors._INHERIT, "could not determine foreground color to inherit")
element.assert(protected.fg_bg.bkg ~= colors._INHERIT, "could not determine background color to inherit")
-- set colors -- set colors
protected.window.setBackgroundColor(protected.fg_bg.bkg) protected.window.setBackgroundColor(protected.fg_bg.bkg)
protected.window.setTextColor(protected.fg_bg.fgd) protected.window.setTextColor(protected.fg_bg.fgd)

View File

@@ -0,0 +1,109 @@
-- App Page Multi-Pane Display Graphics Element
local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element")
local events = require("graphics.events")
local MOUSE_CLICK = core.events.MOUSE_CLICK
---@class app_multipane_args
---@field panes table panes to swap between
---@field nav_colors cpair on/off colors (a/b respectively) for page navigator
---@field scroll_nav boolean? true to allow scrolling to change the active pane
---@field drag_nav boolean? true to allow mouse dragging to change the active pane (on mouse up)
---@field callback function? function to call when pane is changed by mouse interaction
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new app multipane element
---@nodiscard
---@param args app_multipane_args
---@return graphics_element element, element_id id
local function multipane(args)
element.assert(type(args.panes) == "table", "panes is a required field")
-- create new graphics element base object
local e = element.new(args)
e.value = 1
local nav_x_start = math.floor((e.frame.w / 2) - (#args.panes / 2)) + 1
local nav_x_end = math.floor((e.frame.w / 2) - (#args.panes / 2)) + #args.panes
-- show the selected pane
function e.redraw()
for i = 1, #args.panes do args.panes[i].hide() end
args.panes[e.value].show()
-- draw page indicator dots
for i = 1, #args.panes do
e.w_set_cur(nav_x_start + (i - 1), e.frame.h)
e.w_set_fgd(util.trinary(i == e.value, args.nav_colors.color_a, args.nav_colors.color_b))
e.w_write("\x07")
end
end
-- handle mouse interaction
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
local initial = e.value
if e.enabled then
if event.current.y == e.frame.h and event.current.x >= nav_x_start and event.current.x <= nav_x_end then
local id = event.current.x - nav_x_start + 1
if event.type == MOUSE_CLICK.TAP then
e.set_value(id)
elseif event.type == MOUSE_CLICK.UP then
e.set_value(id)
end
end
end
if args.scroll_nav then
if event.type == events.MOUSE_CLICK.SCROLL_DOWN then
e.set_value(e.value + 1)
elseif event.type == events.MOUSE_CLICK.SCROLL_UP then
e.set_value(e.value - 1)
end
end
if args.drag_nav then
local x1, x2 = event.initial.x, event.current.x
if event.type == events.MOUSE_CLICK.UP and e.in_frame_bounds(x1, event.initial.y) and e.in_frame_bounds(x1, event.current.y) then
if x2 > x1 then
e.set_value(e.value - 1)
elseif x2 < x1 then
e.set_value(e.value + 1)
end
end
end
if e.value ~= initial and type(args.callback) == "function" then args.callback(e.value) end
end
-- select which pane is shown
---@param value integer pane to show
function e.set_value(value)
if (e.value ~= value) and (value > 0) and (value <= #args.panes) then
e.value = value
e.redraw()
end
end
-- initial draw
e.redraw()
return e.complete()
end
return multipane

View File

@@ -30,7 +30,7 @@ local function app_button(args)
element.assert(type(args.app_fg_bg) == "table", "app_fg_bg is a required field") element.assert(type(args.app_fg_bg) == "table", "app_fg_bg is a required field")
args.height = 4 args.height = 4
args.width = 5 args.width = 7
-- create new graphics element base object -- create new graphics element base object
local e = element.new(args) local e = element.new(args)
@@ -46,7 +46,7 @@ local function app_button(args)
end end
-- draw icon -- draw icon
e.w_set_cur(1, 1) e.w_set_cur(2, 1)
e.w_set_fgd(fgd) e.w_set_fgd(fgd)
e.w_set_bkg(bkg) e.w_set_bkg(bkg)
e.w_write("\x9f\x83\x83\x83") e.w_write("\x9f\x83\x83\x83")
@@ -55,16 +55,16 @@ local function app_button(args)
e.w_write("\x90") e.w_write("\x90")
e.w_set_fgd(fgd) e.w_set_fgd(fgd)
e.w_set_bkg(bkg) e.w_set_bkg(bkg)
e.w_set_cur(1, 2) e.w_set_cur(2, 2)
e.w_write("\x95 ") e.w_write("\x95 ")
e.w_set_fgd(bkg) e.w_set_fgd(bkg)
e.w_set_bkg(fgd) e.w_set_bkg(fgd)
e.w_write("\x95") e.w_write("\x95")
e.w_set_cur(1, 3) e.w_set_cur(2, 3)
e.w_write("\x82\x8f\x8f\x8f\x81") e.w_write("\x82\x8f\x8f\x8f\x81")
-- write the icon text -- write the icon text
e.w_set_cur(3, 2) e.w_set_cur(4, 2)
e.w_set_fgd(fgd) e.w_set_fgd(fgd)
e.w_set_bkg(bkg) e.w_set_bkg(bkg)
e.w_write(args.text) e.w_write(args.text)

View File

@@ -138,23 +138,21 @@ local function hazard_button(args)
-- handle mouse interaction -- handle mouse interaction
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
if e.enabled then if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) then
if core.events.was_clicked(event.type) then -- change text color to indicate clicked
-- change text color to indicate clicked e.w_set_fgd(args.accent)
e.w_set_fgd(args.accent) e.w_set_cur(3, 2)
e.w_set_cur(3, 2) e.w_write(args.text)
e.w_write(args.text)
-- abort any other callbacks -- abort any other callbacks
tcd.abort(on_timeout) tcd.abort(on_timeout)
tcd.abort(on_success) tcd.abort(on_success)
tcd.abort(on_failure) tcd.abort(on_failure)
-- 1.5 second timeout -- 1.5 second timeout
tcd.dispatch(1.5, on_timeout) tcd.dispatch(1.5, on_timeout)
args.callback() args.callback()
end
end end
end end

View File

@@ -90,7 +90,8 @@ local function radio_button(args)
-- handle mouse interaction -- handle mouse interaction
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
if e.enabled and core.events.was_clicked(event.type) and (event.initial.y == event.current.y) then if e.enabled and core.events.was_clicked(event.type) and
(event.initial.y == event.current.y) and e.in_frame_bounds(event.current.x, event.current.y) then
-- determine what was pressed -- determine what was pressed
if args.options[event.current.y] ~= nil then if args.options[event.current.y] ~= nil then
e.value = event.current.y e.value = event.current.y

View File

@@ -8,13 +8,7 @@ local element = require("graphics.element")
local MOUSE_CLICK = core.events.MOUSE_CLICK local MOUSE_CLICK = core.events.MOUSE_CLICK
---@class sidebar_tab
---@field char string character identifier
---@field color cpair tab colors (fg/bg)
---@class sidebar_args ---@class sidebar_args
---@field tabs table sidebar tab options
---@field callback function function to call on tab change
---@field parent graphics_element ---@field parent graphics_element
---@field id? string element id ---@field id? string element id
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
@@ -27,21 +21,16 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@param args sidebar_args ---@param args sidebar_args
---@return graphics_element element, element_id id ---@return graphics_element element, element_id id
local function sidebar(args) local function sidebar(args)
element.assert(type(args.tabs) == "table", "tabs is a required field")
element.assert(#args.tabs > 0, "at least one tab is required")
element.assert(type(args.callback) == "function", "callback is a required field")
args.width = 3 args.width = 3
-- create new graphics element base object -- create new graphics element base object
local e = element.new(args) local e = element.new(args)
element.assert(e.frame.h >= (#args.tabs * 3), "height insufficent to display all tabs")
-- default to 1st tab -- default to 1st tab
e.value = 1 e.value = 1
local was_pressed = false local was_pressed = false
local tabs = {}
-- show the button state -- show the button state
---@param pressed? boolean if the currently selected tab should appear as actively pressed ---@param pressed? boolean if the currently selected tab should appear as actively pressed
@@ -51,10 +40,18 @@ local function sidebar(args)
was_pressed = pressed was_pressed = pressed
pressed_idx = pressed_idx or e.value pressed_idx = pressed_idx or e.value
for i = 1, #args.tabs do -- clear
local tab = args.tabs[i] ---@type sidebar_tab e.w_set_fgd(e.fg_bg.fgd)
e.w_set_bkg(e.fg_bg.bkg)
for y = 1, e.frame.h do
e.w_set_cur(1, y)
e.w_write(" ")
end
local y = ((i - 1) * 3) + 1 -- draw tabs
for i = 1, #tabs do
local tab = tabs[i] ---@type sidebar_tab
local y = tab.y_start
e.w_set_cur(1, y) e.w_set_cur(1, y)
@@ -66,13 +63,29 @@ local function sidebar(args)
e.w_set_bkg(tab.color.bkg) e.w_set_bkg(tab.color.bkg)
end end
e.w_write(" ") if tab.tall then
e.w_set_cur(1, y + 1) e.w_write(" ")
if e.value == i then e.w_set_cur(1, y + 1)
e.w_write(" " .. tab.char .. "\x10") end
else e.w_write(" " .. tab.char .. " ") end
e.w_set_cur(1, y + 2) e.w_write(tab.label)
e.w_write(" ")
if tab.tall then
e.w_set_cur(1, y + 2)
e.w_write(" ")
end
end
end
-- determine which tab was pressed
---@param y integer y coordinate
local function find_tab(y)
for i = 1, #tabs do
local tab = tabs[i] ---@type sidebar_tab
if y >= tab.y_start and y <= tab.y_end then
return i
end
end end
end end
@@ -81,23 +94,25 @@ local function sidebar(args)
function e.handle_mouse(event) function e.handle_mouse(event)
-- determine what was pressed -- determine what was pressed
if e.enabled then if e.enabled then
local cur_idx = math.ceil(event.current.y / 3) local cur_idx = find_tab(event.current.y)
local ini_idx = math.ceil(event.initial.y / 3) local ini_idx = find_tab(event.initial.y)
local tab = tabs[cur_idx]
if args.tabs[cur_idx] ~= nil then -- handle press if a callback was provided
if tab ~= nil and type(tab.callback) == "function" then
if event.type == MOUSE_CLICK.TAP then if event.type == MOUSE_CLICK.TAP then
e.value = cur_idx e.value = cur_idx
draw(true) draw(true)
-- show as unpressed in 0.25 seconds -- show as unpressed in 0.25 seconds
tcd.dispatch(0.25, function () draw(false) end) tcd.dispatch(0.25, function () draw(false) end)
args.callback(e.value) tab.callback()
elseif event.type == MOUSE_CLICK.DOWN then elseif event.type == MOUSE_CLICK.DOWN then
draw(true, cur_idx) draw(true, cur_idx)
elseif event.type == MOUSE_CLICK.UP then elseif event.type == MOUSE_CLICK.UP then
if cur_idx == ini_idx and e.in_frame_bounds(event.current.x, event.current.y) then if cur_idx == ini_idx and e.in_frame_bounds(event.current.x, event.current.y) then
e.value = cur_idx e.value = cur_idx
draw(false) draw(false)
args.callback(e.value) tab.callback()
else draw(false) end else draw(false) end
end end
elseif event.type == MOUSE_CLICK.UP then elseif event.type == MOUSE_CLICK.UP then
@@ -113,6 +128,35 @@ local function sidebar(args)
draw(false) draw(false)
end end
-- update the sidebar navigation options
---@param items table sidebar entries
function e.on_update(items)
local next_y = 1
tabs = {}
for i = 1, #items do
local item = items[i]
local height = util.trinary(item.tall, 3, 1)
---@class sidebar_tab
local entry = {
y_start = next_y, ---@type integer
y_end = next_y + height - 1, ---@type integer
tall = item.tall, ---@type boolean
label = item.label, ---@type string
color = item.color, ---@type cpair
callback = item.callback ---@type function|nil
}
next_y = next_y + height
tabs[i] = entry
end
draw()
end
-- element redraw -- element redraw
e.redraw = draw e.redraw = draw

View File

@@ -127,20 +127,19 @@ local function spinbox(args)
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
-- only handle if on an increment or decrement arrow -- only handle if on an increment or decrement arrow
if e.enabled and core.events.was_clicked(event.type) and if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) and
(event.current.x ~= dec_point_x) and (event.current.y ~= 2) then (event.current.x ~= dec_point_x) and (event.current.y ~= 2) and
if event.current.x == event.initial.x and event.current.y == event.initial.y then (event.current.x == event.initial.x) and (event.current.y == event.initial.y) then
local idx = util.trinary(event.current.x > dec_point_x, event.current.x - 1, event.current.x) local idx = util.trinary(event.current.x > dec_point_x, event.current.x - 1, event.current.x)
if digits[idx] ~= nil then if digits[idx] ~= nil then
if event.current.y == 1 then if event.current.y == 1 then
digits[idx] = digits[idx] + 1 digits[idx] = digits[idx] + 1
elseif event.current.y == 3 then elseif event.current.y == 3 then
digits[idx] = digits[idx] - 1 digits[idx] = digits[idx] - 1
end
update_value()
show_num()
end end
update_value()
show_num()
end end
end end
end end

View File

@@ -58,14 +58,14 @@ local function switch_button(args)
-- handle mouse interaction -- handle mouse interaction
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
if e.enabled and core.events.was_clicked(event.type) then if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) then
e.value = not e.value e.value = not e.value
e.redraw() e.redraw()
args.callback(e.value) args.callback(e.value)
end end
end end
-- set the value -- set the value (does not call the callback)
---@param val boolean new value ---@param val boolean new value
function e.set_value(val) function e.set_value(val)
e.value = val e.value = val

View File

@@ -98,7 +98,7 @@ local function tabbar(args)
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
-- determine what was pressed -- determine what was pressed
if e.enabled and core.events.was_clicked(event.type) then if e.enabled and core.events.was_clicked(event.type) and e.in_frame_bounds(event.current.x, event.current.y) then
-- a button may have been pressed, which one was it? -- a button may have been pressed, which one was it?
local tab_ini = which_tab(event.initial.x) local tab_ini = which_tab(event.initial.x)
local tab_cur = which_tab(event.current.x) local tab_cur = which_tab(event.current.x)

View File

@@ -53,11 +53,11 @@ local function number_field(args)
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
-- only handle if on an increment or decrement arrow -- only handle if on an increment or decrement arrow
if e.enabled then if e.enabled and e.in_frame_bounds(event.current.x, event.current.y) then
if core.events.was_clicked(event.type) then if core.events.was_clicked(event.type) then
e.take_focus() e.take_focus()
if event.type == MOUSE_CLICK.UP and e.in_frame_bounds(event.current.x, event.current.y) then if event.type == MOUSE_CLICK.UP then
ifield.move_cursor(event.current.x) ifield.move_cursor(event.current.x)
end end
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then

View File

@@ -41,11 +41,11 @@ local function text_field(args)
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
-- only handle if on an increment or decrement arrow -- only handle if on an increment or decrement arrow
if e.enabled then if e.enabled and e.in_frame_bounds(event.current.x, event.current.y) then
if core.events.was_clicked(event.type) then if core.events.was_clicked(event.type) then
e.take_focus() e.take_focus()
if event.type == MOUSE_CLICK.UP and e.in_frame_bounds(event.current.x, event.current.y) then if event.type == MOUSE_CLICK.UP then
ifield.move_cursor(event.current.x) ifield.move_cursor(event.current.x)
end end
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then

View File

@@ -9,7 +9,7 @@ local element = require("graphics.element")
---@class icon_indicator_args ---@class icon_indicator_args
---@field label string indicator label ---@field label string indicator label
---@field states table state color and symbol table ---@field states table state color and symbol table
---@field value? integer default state, defaults to 1 ---@field value? integer|boolean default state, defaults to 1 (true = 2, false = 1)
---@field min_label_width? integer label length if omitted ---@field min_label_width? integer label length if omitted
---@field parent graphics_element ---@field parent graphics_element
---@field id? string element id ---@field id? string element id
@@ -33,6 +33,7 @@ local function icon(args)
local e = element.new(args) local e = element.new(args)
e.value = args.value or 1 e.value = args.value or 1
if e.value == true then e.value = 2 end
-- state blit strings -- state blit strings
local state_blit_cmds = {} local state_blit_cmds = {}
@@ -47,8 +48,11 @@ local function icon(args)
end end
-- on state change -- on state change
---@param new_state integer indicator state ---@param new_state integer|boolean indicator state
function e.on_update(new_state) function e.on_update(new_state)
new_state = new_state or 1
if new_state == true then new_state = 2 end
local blit_cmd = state_blit_cmds[new_state] local blit_cmd = state_blit_cmds[new_state]
e.value = new_state e.value = new_state
e.w_set_cur(1, 1) e.w_set_cur(1, 1)
@@ -56,7 +60,7 @@ local function icon(args)
end end
-- set indicator state -- set indicator state
---@param val integer indicator state ---@param val integer|boolean indicator state
function e.set_value(val) e.on_update(val) end function e.set_value(val) e.on_update(val) end
-- element redraw -- element redraw

View File

@@ -0,0 +1,85 @@
-- Signal Bars Graphics Element
local util = require("scada-common.util")
local element = require("graphics.element")
---@class signal_bar_args
---@field compact? boolean true to use a single character (works better against edges that extend out colors)
---@field colors_low_med? cpair color a for low signal quality, color b for medium signal quality
---@field disconnect_color? color color for the 'x' on disconnect
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors (foreground is used for high signal quality)
---@field hidden? boolean true to hide on initial draw
-- new signal bar
---@nodiscard
---@param args signal_bar_args
---@return graphics_element element, element_id id
local function signal_bar(args)
args.height = 1
args.width = util.trinary(args.compact, 1, 2)
-- create new graphics element base object
local e = element.new(args)
e.value = 0
local blit_bkg = args.fg_bg.blit_bkg
local blit_0, blit_1, blit_2, blit_3 = args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd
if type(args.colors_low_med) == "table" then
blit_1 = args.colors_low_med.blit_a or blit_1
blit_2 = args.colors_low_med.blit_b or blit_2
end
if util.is_int(args.disconnect_color) then blit_0 = colors.toBlit(args.disconnect_color) end
-- on state change (0 = offline, 1 through 3 = low to high signal)
---@param new_state integer signal state
function e.on_update(new_state)
e.value = new_state
e.redraw()
end
-- set signal state (0 = offline, 1 through 3 = low to high signal)
---@param val integer signal state
function e.set_value(val) e.on_update(val) end
-- draw label and signal bar
function e.redraw()
e.w_set_cur(1, 1)
if args.compact then
if e.value == 1 then
e.w_blit("\x90", blit_1, blit_bkg)
elseif e.value == 2 then
e.w_blit("\x94", blit_2, blit_bkg)
elseif e.value == 3 then
e.w_blit("\x95", blit_3, blit_bkg)
else
e.w_blit("x", blit_0, blit_bkg)
end
else
if e.value == 1 then
e.w_blit("\x9f ", blit_bkg .. blit_bkg, blit_1 .. blit_bkg)
elseif e.value == 2 then
e.w_blit("\x9f\x94", blit_bkg .. blit_2, blit_2 .. blit_bkg)
elseif e.value == 3 then
e.w_blit("\x9f\x81", blit_bkg .. blit_bkg, blit_3 .. blit_3)
else
e.w_blit(" x", blit_0 .. blit_0, blit_bkg .. blit_bkg)
end
end
end
-- initial draw
e.redraw()
return e.complete()
end
return signal_bar

418
graphics/themes.lua Normal file
View File

@@ -0,0 +1,418 @@
--
-- Graphics Themes
--
local core = require("graphics.core")
local cpair = core.cpair
---@class graphics_themes
local themes = {}
-- add color mappings for front panels
colors.ivory = colors.pink
colors.green_hc = colors.cyan
colors.yellow_hc = colors.purple
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
--#region Types
---@enum UI_THEME
themes.UI_THEME = { SMOOTH_STONE = 1, DEEPSLATE = 2 }
themes.UI_THEME_NAMES = { "Smooth Stone", "Deepslate" }
-- attempts to get the string name of a main ui theme
---@nodiscard
---@param id any
---@return string|nil
function themes.ui_theme_name(id)
if id == themes.UI_THEME.SMOOTH_STONE or
id == themes.UI_THEME.DEEPSLATE then
return themes.UI_THEME_NAMES[id]
else return nil end
end
---@enum FP_THEME
themes.FP_THEME = { SANDSTONE = 1, BASALT = 2 }
themes.FP_THEME_NAMES = { "Sandstone", "Basalt" }
-- attempts to get the string name of a front panel theme
---@nodiscard
---@param id any
---@return string|nil
function themes.fp_theme_name(id)
if id == themes.FP_THEME.SANDSTONE or
id == themes.FP_THEME.BASALT then
return themes.FP_THEME_NAMES[id]
else return nil end
end
---@enum COLOR_MODE
themes.COLOR_MODE = {
STANDARD = 1,
DEUTERANOPIA = 2,
PROTANOPIA = 3,
TRITANOPIA = 4,
BLUE_IND = 5,
STD_ON_BLACK = 6,
BLUE_ON_BLACK = 7,
NUM_MODES = 8
}
themes.COLOR_MODE_NAMES = {
"Standard",
"Deuteranopia",
"Protanopia",
"Tritanopia",
"Blue for 'Good'",
"Standard + Black",
"Blue + Black"
}
-- attempts to get the string name of a color mode
---@nodiscard
---@param id any
---@return string|nil
function themes.color_mode_name(id)
if id == themes.COLOR_MODE.STANDARD or
id == themes.COLOR_MODE.DEUTERANOPIA or
id == themes.COLOR_MODE.PROTANOPIA or
id == themes.COLOR_MODE.TRITANOPIA or
id == themes.COLOR_MODE.BLUE_IND or
id == themes.COLOR_MODE.STD_ON_BLACK or
id == themes.COLOR_MODE.BLUE_ON_BLACK then
return themes.COLOR_MODE_NAMES[id]
else return nil end
end
--#endregion
--#region Front Panel Themes
---@class fp_theme
themes.sandstone = {
text = colors.black,
label = colors.lightGray,
label_dark = colors.gray,
disabled = colors.lightGray,
bg = colors.ivory,
header = cpair(colors.black, colors.lightGray),
highlight_box = cpair(colors.black, colors.lightGray),
highlight_box_bright = cpair(colors.black, colors.white),
field_box = cpair(colors.gray, colors.white),
colors = {
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xf9fb53 },
{ c = colors.green_off, hex = 0x16665a },
{ c = colors.green, hex = 0x6be551 },
{ c = colors.green_hc, hex = 0x6be551 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.yellow_hc, hex = 0xe3bc2a },
{ c = colors.ivory, hex = 0xdcd9ca },
{ c = colors.yellow_off, hex = 0x85862c },
{ c = colors.white, hex = 0xf0f0f0 },
{ c = colors.lightGray, hex = 0xb1b8b3 },
{ c = colors.gray, hex = 0x575757 },
{ c = colors.black, hex = 0x191919 },
{ c = colors.red_off, hex = 0x672223 }
},
-- color re-mappings for assistive modes
color_modes = {
-- standard
{},
-- deuteranopia
{
{ c = colors.green, hex = 0x1081ff },
{ c = colors.green_hc, hex = 0x1081ff },
{ c = colors.green_off, hex = 0x141414 },
{ c = colors.yellow, hex = 0xf7c311 },
{ c = colors.yellow_off, hex = 0x141414 },
{ c = colors.red, hex = 0xfb5615 },
{ c = colors.red_off, hex = 0x141414 }
},
-- protanopia
{
{ c = colors.green, hex = 0x1081ff },
{ c = colors.green_hc, hex = 0x1081ff },
{ c = colors.green_off, hex = 0x141414 },
{ c = colors.yellow, hex = 0xf5e633 },
{ c = colors.yellow_off, hex = 0x141414 },
{ c = colors.red, hex = 0xff521a },
{ c = colors.red_off, hex = 0x141414 }
},
-- tritanopia
{
{ c = colors.green, hex = 0x40cbd7 },
{ c = colors.green_hc, hex = 0x40cbd7 },
{ c = colors.green_off, hex = 0x141414 },
{ c = colors.yellow, hex = 0xffbc00 },
{ c = colors.yellow_off, hex = 0x141414 },
{ c = colors.red, hex = 0xff0000 },
{ c = colors.red_off, hex = 0x141414 }
},
-- blue indicators
{
{ c = colors.green, hex = 0x1081ff },
{ c = colors.green_hc, hex = 0x1081ff },
{ c = colors.green_off, hex = 0x053466 },
},
-- standard, black backgrounds
{
{ c = colors.green_off, hex = 0x141414 },
{ c = colors.yellow_off, hex = 0x141414 },
{ c = colors.red_off, hex = 0x141414 }
},
-- blue indicators, black backgrounds
{
{ c = colors.green, hex = 0x1081ff },
{ c = colors.green_hc, hex = 0x1081ff },
{ c = colors.green_off, hex = 0x141414 },
{ c = colors.yellow_off, hex = 0x141414 },
{ c = colors.red_off, hex = 0x141414 }
}
}
}
---@type fp_theme
themes.basalt = {
text = colors.white,
label = colors.gray,
label_dark = colors.ivory,
disabled = colors.lightGray,
bg = colors.ivory,
header = cpair(colors.white, colors.gray),
highlight_box = cpair(colors.white, colors.gray),
highlight_box_bright = cpair(colors.black, colors.lightGray),
field_box = cpair(colors.white, colors.gray),
colors = {
{ c = colors.red, hex = 0xf18486 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xefe37c },
{ c = colors.green_off, hex = 0x436b41 },
{ c = colors.green, hex = 0x7ae175 },
{ c = colors.green_hc, hex = 0x7ae175 },
{ c = colors.lightBlue, hex = 0x7dc6f2 },
{ c = colors.blue, hex = 0x56aae6 },
{ c = colors.yellow_hc, hex = 0xe9cd68 },
{ c = colors.ivory, hex = 0x4d4e52 },
{ c = colors.yellow_off, hex = 0x757040 },
{ c = colors.white, hex = 0xbfbfbf },
{ c = colors.lightGray, hex = 0x848794 },
{ c = colors.gray, hex = 0x5c5f68 },
{ c = colors.black, hex = 0x333333 },
{ c = colors.red_off, hex = 0x512d2d }
},
color_modes = {
-- standard
{},
-- deuteranopia
{
{ c = colors.green, hex = 0x65aeff },
{ c = colors.green_hc, hex = 0x99c9ff },
{ c = colors.green_off, hex = 0x333333 },
{ c = colors.yellow, hex = 0xf7c311 },
{ c = colors.yellow_off, hex = 0x333333 },
{ c = colors.red, hex = 0xf18486 },
{ c = colors.red_off, hex = 0x333333 }
},
-- protanopia
{
{ c = colors.green, hex = 0x65aeff },
{ c = colors.green_hc, hex = 0x99c9ff },
{ c = colors.green_off, hex = 0x333333 },
{ c = colors.yellow, hex = 0xf5e633 },
{ c = colors.yellow_off, hex = 0x333333 },
{ c = colors.red, hex = 0xff8058 },
{ c = colors.red_off, hex = 0x333333 }
},
-- tritanopia
{
{ c = colors.green, hex = 0x00ecff },
{ c = colors.green_hc, hex = 0x00ecff },
{ c = colors.green_off, hex = 0x333333 },
{ c = colors.yellow, hex = 0xffbc00 },
{ c = colors.yellow_off, hex = 0x333333 },
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.red_off, hex = 0x333333 }
},
-- blue indicators
{
{ c = colors.green, hex = 0x65aeff },
{ c = colors.green_hc, hex = 0x99c9ff },
{ c = colors.green_off, hex = 0x365e8a },
},
-- standard, black backgrounds
{
{ c = colors.green_off, hex = 0x333333 },
{ c = colors.yellow_off, hex = 0x333333 },
{ c = colors.red_off, hex = 0x333333 }
},
-- blue indicators, black backgrounds
{
{ c = colors.green, hex = 0x65aeff },
{ c = colors.green_hc, hex = 0x99c9ff },
{ c = colors.green_off, hex = 0x333333 },
{ c = colors.yellow_off, hex = 0x333333 },
{ c = colors.red_off, hex = 0x333333 }
}
}
}
-- get style fields for a front panel based on the provided theme
---@param theme fp_theme
function themes.get_fp_style(theme)
---@class fp_style
local style = {
root = cpair(theme.text, theme.bg),
text = cpair(theme.text, theme.bg),
text_fg = cpair(theme.text, colors._INHERIT),
label_fg = cpair(theme.label, colors._INHERIT),
label_d_fg = cpair(theme.label_dark, colors._INHERIT),
disabled_fg = cpair(theme.disabled, colors._INHERIT)
}
return style
end
--#endregion
--#region Main UI Color Palettes
---@class ui_palette
themes.smooth_stone = {
colors = {
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.lime, hex = 0x80ff80 },
{ c = colors.green, hex = 0x4aee8a },
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee },
{ c = colors.pink, hex = 0xf26ba2 },
{ c = colors.magenta, hex = 0xf9488a },
{ c = colors.white, hex = 0xf0f0f0 },
{ c = colors.lightGray, hex = 0xcacaca },
{ c = colors.gray, hex = 0x575757 },
{ c = colors.black, hex = 0x191919 },
{ c = colors.brown, hex = 0x7f664c }
},
-- color re-mappings for assistive modes
color_modes = {
-- standard
{},
-- deuteranopia
{
{ c = colors.blue, hex = 0x1081ff },
{ c = colors.yellow, hex = 0xf7c311 },
{ c = colors.red, hex = 0xfb5615 }
},
-- protanopia
{
{ c = colors.blue, hex = 0x1081ff },
{ c = colors.yellow, hex = 0xf5e633 },
{ c = colors.red, hex = 0xff521a }
},
-- tritanopia
{
{ c = colors.blue, hex = 0x40cbd7 },
{ c = colors.yellow, hex = 0xffbc00 },
{ c = colors.red, hex = 0xff0000 }
},
-- blue indicators
{
{ c = colors.blue, hex = 0x1081ff },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.red, hex = 0xdf4949 }
},
-- standard, black backgrounds
{},
-- blue indicators, black backgrounds
{
{ c = colors.blue, hex = 0x1081ff },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.red, hex = 0xdf4949 }
}
}
}
---@type ui_palette
themes.deepslate = {
colors = {
{ c = colors.red, hex = 0xeb6a6c },
{ c = colors.orange, hex = 0xf2b86c },
{ c = colors.yellow, hex = 0xd9cf81 },
{ c = colors.lime, hex = 0x80ff80 },
{ c = colors.green, hex = 0x70e19b },
{ c = colors.cyan, hex = 0x7ccdd0 },
{ c = colors.lightBlue, hex = 0x99ceef },
{ c = colors.blue, hex = 0x60bcff },
{ c = colors.purple, hex = 0xc38aea },
{ c = colors.pink, hex = 0xff7fb8 },
{ c = colors.magenta, hex = 0xf980dd },
{ c = colors.white, hex = 0xd9d9d9 },
{ c = colors.lightGray, hex = 0x949494 },
{ c = colors.gray, hex = 0x575757 },
{ c = colors.black, hex = 0x262626 },
{ c = colors.brown, hex = 0xb18f6a }
},
-- color re-mappings for assistive modes
color_modes = {
-- standard
{},
-- deuteranopia
{
{ c = colors.blue, hex = 0x65aeff },
{ c = colors.yellow, hex = 0xf7c311 },
{ c = colors.red, hex = 0xfb5615 }
},
-- protanopia
{
{ c = colors.blue, hex = 0x65aeff },
{ c = colors.yellow, hex = 0xf5e633 },
{ c = colors.red, hex = 0xff8058 }
},
-- tritanopia
{
{ c = colors.blue, hex = 0x00ecff },
{ c = colors.yellow, hex = 0xffbc00 },
{ c = colors.red, hex = 0xdf4949 }
},
-- blue indicators
{
{ c = colors.blue, hex = 0x65aeff },
{ c = colors.yellow, hex = 0xd9cf81 },
{ c = colors.red, hex = 0xeb6a6c }
},
-- standard, black backgrounds
{},
-- blue indicators, black backgrounds
{
{ c = colors.blue, hex = 0x65aeff },
{ c = colors.yellow, hex = 0xd9cf81 },
{ c = colors.red, hex = 0xeb6a6c }
}
}
}
--#endregion
return themes

View File

@@ -7,6 +7,7 @@ local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
local themes = require("graphics.themes")
local DisplayBox = require("graphics.elements.displaybox") local DisplayBox = require("graphics.elements.displaybox")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
@@ -41,21 +42,7 @@ local style = {}
style.root = cpair(colors.black, colors.lightGray) style.root = cpair(colors.black, colors.lightGray)
style.header = cpair(colors.white, colors.gray) style.header = cpair(colors.white, colors.gray)
style.colors = { style.colors = themes.smooth_stone.colors
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.lime, hex = 0x80ff80 },
{ c = colors.green, hex = 0x4aee8a },
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee },
{ c = colors.pink, hex = 0xf26ba2 },
{ c = colors.magenta, hex = 0xf9488a },
{ c = colors.lightGray, hex = 0xcacaca },
{ c = colors.gray, hex = 0x575757 }
}
local bw_fg_bg = cpair(colors.black, colors.white) local bw_fg_bg = cpair(colors.black, colors.white)
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray) local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
@@ -112,7 +99,7 @@ local fields = {
{ "AuthKey", "Facility Auth Key" , ""}, { "AuthKey", "Facility Auth Key" , ""},
{ "LogMode", "Log Mode", log.MODE.APPEND }, { "LogMode", "Log Mode", log.MODE.APPEND },
{ "LogPath", "Log Path", "/log.txt" }, { "LogPath", "Log Path", "/log.txt" },
{ "LogDebug","Log Debug Messages", false } { "LogDebug", "Log Debug Messages", false }
} }
-- load data from the settings file -- load data from the settings file

View File

@@ -2,19 +2,20 @@
-- I/O Control for Pocket Integration with Supervisor & Coordinator -- I/O Control for Pocket Integration with Supervisor & Coordinator
-- --
local psil = require("scada-common.psil") local log = require("scada-common.log")
local psil = require("scada-common.psil")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util")
local ALARM = types.ALARM local ALARM = types.ALARM
local ALARM_STATE = types.ALARM_STATE
---@todo nominal trip time is ping (0ms to 10ms usually)
local WARN_TT = 40
local HIGH_TT = 80
local iocontrol = {} local iocontrol = {}
---@class pocket_ioctl
local io = {
ps = psil.create()
}
---@enum POCKET_LINK_STATE ---@enum POCKET_LINK_STATE
local LINK_STATE = { local LINK_STATE = {
UNLINKED = 0, UNLINKED = 0,
@@ -23,23 +24,203 @@ local LINK_STATE = {
LINKED = 3 LINKED = 3
} }
---@enum NAV_PAGE iocontrol.LINK_STATE = LINK_STATE
local NAV_PAGE = {
HOME = 1, ---@enum POCKET_APP_ID
local APP_ID = {
ROOT = 1,
-- main app page
UNITS = 2, UNITS = 2,
REACTORS = 3, ABOUT = 3,
BOILERS = 4, -- diag app page
TURBINES = 5, ALARMS = 4,
DIAG = 6, -- other
D_ALARMS = 7 DUMMY = 5,
NUM_APPS = 5
} }
iocontrol.LINK_STATE = LINK_STATE iocontrol.APP_ID = APP_ID
iocontrol.NAV_PAGE = NAV_PAGE
---@class pocket_ioctl
local io = {
version = "unknown",
ps = psil.create()
}
---@class nav_tree_page
---@field _p nav_tree_page|nil page's parent
---@field _c table page's children
---@field nav_to function function to navigate to this page
---@field switcher function|nil function to switch between children
---@field tasks table tasks to run while viewing this page
-- allocate the page navigation system
function iocontrol.alloc_nav()
local self = {
pane = nil, ---@type graphics_element
apps = {},
containers = {},
cur_app = APP_ID.ROOT
}
self.cur_page = self.root
---@class pocket_nav
io.nav = {}
-- set the root pane element to switch between apps with
---@param root_pane graphics_element
function io.nav.set_pane(root_pane)
self.pane = root_pane
end
function io.nav.set_sidebar(sidebar)
self.sidebar = sidebar
end
-- register an app
---@param app_id POCKET_APP_ID app ID
---@param container graphics_element element that contains this app (usually a Div)
---@param pane graphics_element? multipane if this is a simple paned app, then nav_to must be a number
function io.nav.register_app(app_id, container, pane)
---@class pocket_app
local app = {
loaded = false,
load = nil,
cur_page = nil, ---@type nav_tree_page
pane = pane,
paned_pages = {},
sidebar_items = {}
}
app.load = function () app.loaded = true end
-- delayed set of the pane if it wasn't ready at the start
---@param root_pane graphics_element multipane
function app.set_root_pane(root_pane)
app.pane = root_pane
end
function app.set_sidebar(items)
app.sidebar_items = items
if self.sidebar then self.sidebar.update(items) end
end
-- function to run on initial load into memory
---@param on_load function callback
function app.set_on_load(on_load)
app.load = function ()
on_load()
app.loaded = true
end
end
-- if a pane was provided, this will switch between numbered pages
---@param idx integer page index
function app.switcher(idx)
if app.paned_pages[idx] then
app.paned_pages[idx].nav_to()
end
end
-- create a new page entry in the app's page navigation tree
---@param parent nav_tree_page? a parent page or nil to set this as the root
---@param nav_to function|integer function to navigate to this page or pane index
---@return nav_tree_page new_page this new page
function app.new_page(parent, nav_to)
---@type nav_tree_page
local page = { _p = parent, _c = {}, nav_to = function () end, switcher = function () end, tasks = {} }
if parent == nil and app.cur_page == nil then
app.cur_page = page
end
if type(nav_to) == "number" then
app.paned_pages[nav_to] = page
function page.nav_to()
app.cur_page = page
if app.pane then app.pane.set_value(nav_to) end
end
else
function page.nav_to()
app.cur_page = page
nav_to()
end
end
-- switch between children
---@param id integer child ID
function page.switcher(id) if page._c[id] then page._c[id].nav_to() end end
if parent ~= nil then
table.insert(page._p._c, page)
end
return page
end
-- get the currently active page
function app.get_current_page() return app.cur_page end
-- attempt to navigate up the tree
---@return boolean success true if successfully navigated up
function app.nav_up()
local parent = app.cur_page._p
if parent then parent.nav_to() end
return parent ~= nil
end
self.apps[app_id] = app
self.containers[app_id] = container
return app
end
-- get a list of the app containers (usually Div elements)
function io.nav.get_containers() return self.containers end
-- open a given app
---@param app_id POCKET_APP_ID
function io.nav.open_app(app_id)
local app = self.apps[app_id] ---@type pocket_app
if app then
if not app.loaded then app.load() end
self.cur_app = app_id
self.pane.set_value(app_id)
if #app.sidebar_items > 0 then
self.sidebar.update(app.sidebar_items)
end
else
log.debug("tried to open unknown app")
end
end
-- get the currently active page
---@return nav_tree_page
function io.nav.get_current_page()
return self.apps[self.cur_app].get_current_page()
end
-- attempt to navigate up
function io.nav.nav_up()
local app = self.apps[self.cur_app] ---@type pocket_app
log.debug("attempting app nav up for app " .. self.cur_app)
if not app.nav_up() then
log.debug("internal app nav up failed, going to home screen")
io.nav.open_app(APP_ID.ROOT)
end
end
end
-- initialize facility-independent components of pocket iocontrol -- initialize facility-independent components of pocket iocontrol
---@param comms pocket_comms ---@param comms pocket_comms
function iocontrol.init_core(comms) function iocontrol.init_core(comms)
iocontrol.alloc_nav()
---@class pocket_ioctl_diag ---@class pocket_ioctl_diag
io.diag = {} io.diag = {}
@@ -77,28 +258,520 @@ function iocontrol.init_core(comms)
tone_indicators = {} -- indicators to update from supervisor tone states tone_indicators = {} -- indicators to update from supervisor tone states
} }
---@class pocket_nav -- API access
io.nav = { ---@class pocket_ioctl_api
page = NAV_PAGE.HOME, ---@type NAV_PAGE io.api = {
sub_pages = { NAV_PAGE.HOME, NAV_PAGE.UNITS, NAV_PAGE.REACTORS, NAV_PAGE.BOILERS, NAV_PAGE.TURBINES, NAV_PAGE.DIAG }, get_unit = function (unit) comms.api__get_unit(unit) end
tasks = {}
} }
-- add a task to be performed periodically while on a given page
---@param page NAV_PAGE page to add task to
---@param task function function to execute
function io.nav.register_task(page, task)
if io.nav.tasks[page] == nil then io.nav.tasks[page] = {} end
table.insert(io.nav.tasks[page], task)
end
end end
-- initialize facility-dependent components of pocket iocontrol -- initialize facility-dependent components of pocket iocontrol
function iocontrol.init_fac() end ---@param conf facility_conf configuration
---@param temp_scale 1|2|3|4 temperature unit (1 = K, 2 = C, 3 = F, 4 = R)
function iocontrol.init_fac(conf, temp_scale)
-- temperature unit label and conversion function (from Kelvin)
if temp_scale == 2 then
io.temp_label = "\xb0C"
io.temp_convert = function (t) return t - 273.15 end
elseif temp_scale == 3 then
io.temp_label = "\xb0F"
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
elseif temp_scale == 4 then
io.temp_label = "\xb0R"
io.temp_convert = function (t) return 1.8 * t end
else
io.temp_label = "K"
io.temp_convert = function (t) return t end
end
-- facility data structure
---@class pioctl_facility
io.facility = {
num_units = conf.num_units,
tank_mode = conf.cooling.fac_tank_mode,
tank_defs = conf.cooling.fac_tank_defs,
all_sys_ok = false,
rtu_count = 0,
auto_ready = false,
auto_active = false,
auto_ramping = false,
auto_saturated = false,
auto_scram = false,
---@type ascram_status
ascram_status = {
matrix_dc = false,
matrix_fill = false,
crit_alarm = false,
radiation = false,
gen_fault = false
},
---@type WASTE_PRODUCT
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
auto_pu_fallback_active = false,
radiation = types.new_zero_radiation_reading(),
ps = psil.create(),
induction_ps_tbl = {},
induction_data_tbl = {},
sps_ps_tbl = {},
sps_data_tbl = {},
tank_ps_tbl = {},
tank_data_tbl = {},
env_d_ps = psil.create(),
env_d_data = {}
}
-- create induction and SPS tables (currently only 1 of each is supported)
table.insert(io.facility.induction_ps_tbl, psil.create())
table.insert(io.facility.induction_data_tbl, {})
table.insert(io.facility.sps_ps_tbl, psil.create())
table.insert(io.facility.sps_data_tbl, {})
-- determine tank information
if io.facility.tank_mode == 0 then
io.facility.tank_defs = {}
-- on facility tank mode 0, setup tank defs to match unit tank option
for i = 1, conf.num_units do
io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TankConnection, 1, 0)
end
io.facility.tank_list = { table.unpack(io.facility.tank_defs) }
else
-- decode the layout of tanks from the connections definitions
local tank_mode = io.facility.tank_mode
local tank_defs = io.facility.tank_defs
local tank_list = { table.unpack(tank_defs) }
local function calc_fdef(start_idx, end_idx)
local first = 4
for i = start_idx, end_idx do
if io.facility.tank_defs[i] == 2 then
if i < first then first = i end
end
end
return first
end
if tank_mode == 1 then
-- (1) 1 total facility tank (A A A A)
local first_fdef = calc_fdef(1, #tank_defs)
for i = 1, #tank_defs do
if i > first_fdef and tank_defs[i] == 2 then
tank_list[i] = 0
end
end
elseif tank_mode == 2 then
-- (2) 2 total facility tanks (A A A B)
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
for i = 1, #tank_defs do
if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 3 then
-- (3) 2 total facility tanks (A A B B)
for _, a in pairs({ 1, 3 }) do
local b = a + 1
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
tank_list[b] = 0
end
end
elseif tank_mode == 4 then
-- (4) 2 total facility tanks (A B B B)
local first_fdef = calc_fdef(2, #tank_defs)
for i = 1, #tank_defs do
if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 5 then
-- (5) 3 total facility tanks (A A B C)
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
for i = 1, #tank_defs do
if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 6 then
-- (6) 3 total facility tanks (A B B C)
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
for i = 1, #tank_defs do
if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
elseif tank_mode == 7 then
-- (7) 3 total facility tanks (A B C C)
local first_fdef = calc_fdef(3, #tank_defs)
for i = 1, #tank_defs do
if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then
tank_list[i] = 0
end
end
end
io.facility.tank_list = tank_list
end
-- create facility tank tables
for i = 1, #io.facility.tank_list do
if io.facility.tank_list[i] == 2 then
table.insert(io.facility.tank_ps_tbl, psil.create())
table.insert(io.facility.tank_data_tbl, {})
end
end
-- create unit data structures
io.units = {}
for i = 1, conf.num_units do
---@class pioctl_unit
local entry = {
unit_id = i,
connected = false,
rtu_hw = {},
num_boilers = 0,
num_turbines = 0,
num_snas = 0,
has_tank = conf.cooling.r_cool[i].TankConnection,
control_state = false,
burn_rate_cmd = 0.0,
radiation = types.new_zero_radiation_reading(),
sna_peak_rate = 0.0,
sna_max_rate = 0.0,
sna_out_rate = 0.0,
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
-- auto control group
a_group = 0,
---@type alarms
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
annunciator = {}, ---@type annunciator
unit_ps = psil.create(),
reactor_data = {}, ---@type reactor_db
boiler_ps_tbl = {},
boiler_data_tbl = {},
turbine_ps_tbl = {},
turbine_data_tbl = {},
tank_ps_tbl = {},
tank_data_tbl = {}
}
-- on other facility modes, overwrite unit TANK option with facility tank defs
if io.facility.tank_mode ~= 0 then
entry.has_tank = conf.cooling.fac_tank_defs[i] > 0
end
-- create boiler tables
for _ = 1, conf.cooling.r_cool[i].BoilerCount do
table.insert(entry.boiler_ps_tbl, psil.create())
table.insert(entry.boiler_data_tbl, {})
end
-- create turbine tables
for _ = 1, conf.cooling.r_cool[i].TurbineCount do
table.insert(entry.turbine_ps_tbl, psil.create())
table.insert(entry.turbine_data_tbl, {})
end
-- create tank tables
if io.facility.tank_defs[i] == 1 then
table.insert(entry.tank_ps_tbl, psil.create())
table.insert(entry.tank_data_tbl, {})
end
entry.num_boilers = #entry.boiler_data_tbl
entry.num_turbines = #entry.turbine_data_tbl
table.insert(io.units, entry)
end
end
-- set network link state -- set network link state
---@param state POCKET_LINK_STATE ---@param state POCKET_LINK_STATE
function iocontrol.report_link_state(state) io.ps.publish("link_state", state) end ---@param sv_addr integer? supervisor address if linked
---@param api_addr integer? coordinator address if linked
function iocontrol.report_link_state(state, sv_addr, api_addr)
io.ps.publish("link_state", state)
if state == LINK_STATE.API_LINK_ONLY or state == LINK_STATE.UNLINKED then
io.ps.publish("svr_conn_quality", 0)
end
if state == LINK_STATE.SV_LINK_ONLY or state == LINK_STATE.UNLINKED then
io.ps.publish("crd_conn_quality", 0)
end
if state == LINK_STATE.LINKED then
io.ps.publish("sv_addr", sv_addr)
io.ps.publish("api_addr", api_addr)
end
end
-- determine supervisor connection quality (trip time)
---@param trip_time integer
function iocontrol.report_svr_tt(trip_time)
local state = 3
if trip_time > HIGH_TT then
state = 1
elseif trip_time > WARN_TT then
state = 2
end
io.ps.publish("svr_conn_quality", state)
end
-- determine coordinator connection quality (trip time)
---@param trip_time integer
function iocontrol.report_crd_tt(trip_time)
local state = 3
if trip_time > HIGH_TT then
state = 1
elseif trip_time > WARN_TT then
state = 2
end
io.ps.publish("crd_conn_quality", state)
end
-- populate facility data from API_GET_FAC
---@param data table
---@return boolean valid
function iocontrol.record_facility_data(data)
local valid = true
local fac = io.facility
fac.all_sys_ok = data[1]
fac.rtu_count = data[2]
fac.radiation = data[3]
-- auto control
if type(data[4]) == "table" and #data[4] == 4 then
fac.auto_ready = data[4][1]
fac.auto_active = data[4][2]
fac.auto_ramping = data[4][3]
fac.auto_saturated = data[4][4]
end
-- waste
if type(data[5]) == "table" and #data[5] == 2 then
fac.auto_current_waste_product = data[5][1]
fac.auto_pu_fallback_active = data[5][2]
end
fac.num_tanks = data[6]
fac.has_imatrix = data[7]
fac.has_sps = data[8]
return valid
end
-- update unit status data from API_GET_UNIT
---@param data table
function iocontrol.record_unit_data(data)
if type(data[1]) == "number" and io.units[data[1]] then
local unit = io.units[data[1]] ---@type pioctl_unit
unit.connected = data[2]
unit.rtu_hw = data[3]
unit.alarms = data[4]
--#region Annunciator
unit.annunciator = data[5]
local rcs_disconn, rcs_warn, rcs_hazard = false, false, false
for key, val in pairs(unit.annunciator) do
if key == "BoilerOnline" or key == "TurbineOnline" then
local every = true
-- split up online arrays
for id = 1, #val do
every = every and val[id]
if key == "BoilerOnline" then
unit.boiler_ps_tbl[id].publish(key, val[id])
else
unit.turbine_ps_tbl[id].publish(key, val[id])
end
end
if not every then rcs_disconn = true end
unit.unit_ps.publish("U_" .. key, every)
elseif key == "HeatingRateLow" or key == "WaterLevelLow" then
-- split up array for all boilers
local any = false
for id = 1, #val do
any = any or val[id]
unit.boiler_ps_tbl[id].publish(key, val[id])
end
if key == "HeatingRateLow" and any then
rcs_warn = true
elseif key == "WaterLevelLow" and any then
rcs_hazard = true
end
unit.unit_ps.publish("U_" .. key, any)
elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then
-- split up array for all turbines
local any = false
for id = 1, #val do
any = any or val[id]
unit.turbine_ps_tbl[id].publish(key, val[id])
end
if key == "GeneratorTrip" and any then
rcs_warn = true
elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then
rcs_hazard = true
end
unit.unit_ps.publish("U_" .. key, any)
else
-- non-table fields
unit.unit_ps.publish(key, val)
end
end
local anc = unit.annunciator
rcs_hazard = rcs_hazard or anc.RCPTrip
rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or
anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch or anc.MaxWaterReturnFeed
local rcs_status = 4
if rcs_hazard then
rcs_status = 2
elseif rcs_warn then
rcs_status = 3
elseif rcs_disconn then
rcs_status = 1
end
unit.unit_ps.publish("U_RCS", rcs_status)
--#endregion
--#region Reactor Data
unit.reactor_data = data[6]
local control_status = 1
local reactor_status = 1
local rps_status = 1
if unit.connected then
-- update RPS status
if unit.reactor_data.rps_tripped then
control_status = 2
rps_status = util.trinary(unit.reactor_data.rps_trip_cause == "manual", 3, 2)
else rps_status = 4 end
-- update reactor/control status
if unit.reactor_data.mek_status.status then
reactor_status = 4
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
else
if unit.reactor_data.no_reactor then
reactor_status = 2
elseif not unit.reactor_data.formed or unit.reactor_data.rps_status.force_dis then
reactor_status = 3
else
reactor_status = 4
end
end
for key, val in pairs(unit.reactor_data) do
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
unit.unit_ps.publish(key, val)
end
end
if type(unit.reactor_data.rps_status) == "table" then
for key, val in pairs(unit.reactor_data.rps_status) do
unit.unit_ps.publish(key, val)
end
end
if type(unit.reactor_data.mek_status) == "table" then
for key, val in pairs(unit.reactor_data.mek_status) do
unit.unit_ps.publish(key, val)
end
end
end
unit.unit_ps.publish("U_ControlStatus", control_status)
unit.unit_ps.publish("U_ReactorStatus", reactor_status)
unit.unit_ps.publish("U_RPS", rps_status)
--#endregion
unit.boiler_data_tbl = data[7]
for id = 1, #unit.boiler_data_tbl do
local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db
local ps = unit.boiler_ps_tbl[id] ---@type psil
local boiler_status = 1
if unit.rtu_hw.boilers[id].connected then
if unit.rtu_hw.boilers[id].faulted then
boiler_status = 3
elseif boiler.formed then
boiler_status = 4
else
boiler_status = 2
end
end
ps.publish("BoilerStatus", boiler_status)
end
unit.turbine_data_tbl = data[8]
for id = 1, #unit.turbine_data_tbl do
local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db
local ps = unit.turbine_ps_tbl[id] ---@type psil
local turbine_status = 1
if unit.rtu_hw.turbines[id].connected then
if unit.rtu_hw.turbines[id].faulted then
turbine_status = 3
elseif turbine.formed then
turbine_status = 4
else
turbine_status = 2
end
end
ps.publish("TurbineStatus", turbine_status)
end
unit.tank_data_tbl = data[9]
end
end
-- get the IO controller database -- get the IO controller database
function iocontrol.get_db() return io end function iocontrol.get_db() return io end

View File

@@ -8,6 +8,7 @@ local PROTOCOL = comms.PROTOCOL
local DEVICE_TYPE = comms.DEVICE_TYPE local DEVICE_TYPE = comms.DEVICE_TYPE
local ESTABLISH_ACK = comms.ESTABLISH_ACK local ESTABLISH_ACK = comms.ESTABLISH_ACK
local MGMT_TYPE = comms.MGMT_TYPE local MGMT_TYPE = comms.MGMT_TYPE
local CRDN_TYPE = comms.CRDN_TYPE
local LINK_STATE = iocontrol.LINK_STATE local LINK_STATE = iocontrol.LINK_STATE
@@ -118,6 +119,20 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
self.api.seq_num = self.api.seq_num + 1 self.api.seq_num = self.api.seq_num + 1
end end
-- send an API packet to the coordinator
---@param msg_type CRDN_TYPE
---@param msg table
local function _send_api(msg_type, msg)
local s_pkt = comms.scada_packet()
local pkt = comms.crdn_packet()
pkt.make(msg_type, msg)
s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_CRDN, pkt.raw_sendable())
nic.transmit(config.CRD_Channel, config.PKT_Channel, s_pkt)
self.api.seq_num = self.api.seq_num + 1
end
-- attempt supervisor connection establishment -- attempt supervisor connection establishment
local function _send_sv_establish() local function _send_sv_establish()
_send_sv(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT }) _send_sv(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT })
@@ -125,7 +140,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
-- attempt coordinator API connection establishment -- attempt coordinator API connection establishment
local function _send_api_establish() local function _send_api_establish()
_send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT }) _send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT, comms.api_version })
end end
-- keep alive ack to supervisor -- keep alive ack to supervisor
@@ -191,7 +206,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
end end
else else
-- linked, all good! -- linked, all good!
iocontrol.report_link_state(LINK_STATE.LINKED) iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, self.api.addr)
end end
end end
@@ -214,6 +229,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
end end
-- coordinator get unit data
function public.api__get_unit(unit)
if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end
end
-- parse a packet -- parse a packet
---@param side string ---@param side string
---@param sender integer ---@param sender integer
@@ -246,6 +266,25 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
return pkt return pkt
end end
---@param packet mgmt_frame|crdn_frame
---@param length integer
---@param max integer?
---@return boolean
local function _check_length(packet, length, max)
local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= (max or 0))
if not ok then
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d"
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
end
return ok
end
---@param packet mgmt_frame|crdn_frame
local function _fail_type(packet)
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: unrecognized packet type"
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
end
-- handle a packet -- handle a packet
---@param packet mgmt_frame|crdn_frame|nil ---@param packet mgmt_frame|crdn_frame|nil
function public.handle_packet(packet) function public.handle_packet(packet)
@@ -268,7 +307,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
return return
elseif self.api.linked and (src_addr ~= self.api.addr) then elseif self.api.linked and (src_addr ~= self.api.addr) then
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr .. log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
"); channel in use by another system?") "); channel in use by another system?")
return return
else else
self.api.r_seq_num = packet.scada_frame.seq_num() self.api.r_seq_num = packet.scada_frame.seq_num()
@@ -277,12 +316,27 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
-- feed watchdog on valid sequence number -- feed watchdog on valid sequence number
api_watchdog.feed() api_watchdog.feed()
if protocol == PROTOCOL.SCADA_MGMT then if protocol == PROTOCOL.SCADA_CRDN then
---@cast packet crdn_frame
if self.api.linked then
if packet.type == CRDN_TYPE.API_GET_FAC then
if _check_length(packet, 11) then
iocontrol.record_facility_data(packet.data)
end
elseif packet.type == CRDN_TYPE.API_GET_UNIT then
if _check_length(packet, 9) then
iocontrol.record_unit_data(packet.data)
end
else _fail_type(packet) end
else
log.debug("discarding coordinator SCADA_CRDN packet before linked")
end
elseif protocol == PROTOCOL.SCADA_MGMT then
---@cast packet mgmt_frame ---@cast packet mgmt_frame
if self.api.linked then if self.api.linked then
if packet.type == MGMT_TYPE.KEEP_ALIVE then if packet.type == MGMT_TYPE.KEEP_ALIVE then
-- keep alive request received, echo back -- keep alive request received, echo back
if packet.length == 1 then if _check_length(packet, 1) then
local timestamp = packet.data[1] local timestamp = packet.data[1]
local trip_time = util.time() - timestamp local trip_time = util.time() - timestamp
@@ -290,11 +344,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
end end
-- log.debug("pocket coordinator RTT = " .. trip_time .. "ms") -- log.debug("pocket coordinator TT = " .. trip_time .. "ms")
_send_api_keep_alive_ack(timestamp) _send_api_keep_alive_ack(timestamp)
else
log.debug("coordinator SCADA keep alive packet length mismatch") iocontrol.report_crd_tt(trip_time)
end end
elseif packet.type == MGMT_TYPE.CLOSE then elseif packet.type == MGMT_TYPE.CLOSE then
-- handle session close -- handle session close
@@ -303,24 +357,38 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
self.api.r_seq_num = nil self.api.r_seq_num = nil
self.api.addr = comms.BROADCAST self.api.addr = comms.BROADCAST
log.info("coordinator server connection closed by remote host") log.info("coordinator server connection closed by remote host")
else else _fail_type(packet) end
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator")
end
elseif packet.type == MGMT_TYPE.ESTABLISH then elseif packet.type == MGMT_TYPE.ESTABLISH then
-- connection with coordinator established -- connection with coordinator established
if packet.length == 1 then if _check_length(packet, 1, 2) then
local est_ack = packet.data[1] local est_ack = packet.data[1]
if est_ack == ESTABLISH_ACK.ALLOW then if est_ack == ESTABLISH_ACK.ALLOW then
log.info("coordinator connection established") if packet.length == 2 then
self.establish_delay_counter = 0 local fac_config = packet.data[2]
self.api.linked = true
self.api.addr = src_addr
if self.sv.linked then if type(fac_config) == "table" and #fac_config == 2 then
iocontrol.report_link_state(LINK_STATE.LINKED) -- get configuration
local conf = { num_units = fac_config[1], cooling = fac_config[2] }
---@todo unit options
iocontrol.init_fac(conf, 1)
log.info("coordinator connection established")
self.establish_delay_counter = 0
self.api.linked = true
self.api.addr = src_addr
if self.sv.linked then
iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, self.api.addr)
else
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
end
else
log.debug("invalid facility configuration table received from coordinator, establish failed")
end
else else
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY) log.debug("received coordinator establish allow without facility configuration")
end end
elseif est_ack == ESTABLISH_ACK.DENY then elseif est_ack == ESTABLISH_ACK.DENY then
if self.api.last_est_ack ~= est_ack then if self.api.last_est_ack ~= est_ack then
@@ -334,13 +402,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
if self.api.last_est_ack ~= est_ack then if self.api.last_est_ack ~= est_ack then
log.info("coordinator comms version mismatch") log.info("coordinator comms version mismatch")
end end
elseif est_ack == ESTABLISH_ACK.BAD_API_VERSION then
if self.api.last_est_ack ~= est_ack then
log.info("coordinator api version mismatch")
end
else else
log.debug("coordinator SCADA_MGMT establish packet reply unsupported") log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
end end
self.api.last_est_ack = est_ack self.api.last_est_ack = est_ack
else
log.debug("coordinator SCADA_MGMT establish packet length mismatch")
end end
else else
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked") log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
@@ -372,7 +442,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
if self.sv.linked then if self.sv.linked then
if packet.type == MGMT_TYPE.KEEP_ALIVE then if packet.type == MGMT_TYPE.KEEP_ALIVE then
-- keep alive request received, echo back -- keep alive request received, echo back
if packet.length == 1 then if _check_length(packet, 1) then
local timestamp = packet.data[1] local timestamp = packet.data[1]
local trip_time = util.time() - timestamp local trip_time = util.time() - timestamp
@@ -380,11 +450,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
end end
-- log.debug("pocket supervisor RTT = " .. trip_time .. "ms") -- log.debug("pocket supervisor TT = " .. trip_time .. "ms")
_send_sv_keep_alive_ack(timestamp) _send_sv_keep_alive_ack(timestamp)
else
log.debug("supervisor SCADA keep alive packet length mismatch") iocontrol.report_svr_tt(trip_time)
end end
elseif packet.type == MGMT_TYPE.CLOSE then elseif packet.type == MGMT_TYPE.CLOSE then
-- handle session close -- handle session close
@@ -394,12 +464,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
self.sv.addr = comms.BROADCAST self.sv.addr = comms.BROADCAST
log.info("supervisor server connection closed by remote host") log.info("supervisor server connection closed by remote host")
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
if packet.length == 8 then if _check_length(packet, 8) then
for i = 1, #packet.data do for i = 1, #packet.data do
diag.tone_test.tone_indicators[i].update(packet.data[i] == true) diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
end end
else
log.debug("supervisor SCADA diag alarm states packet length mismatch")
end end
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
if packet.length == 1 and packet.data[1] == false then if packet.length == 1 and packet.data[1] == false then
@@ -438,12 +506,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
else else
log.debug("supervisor SCADA diag alarm set packet length/type mismatch") log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
end end
else else _fail_type(packet) end
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
end
elseif packet.type == MGMT_TYPE.ESTABLISH then elseif packet.type == MGMT_TYPE.ESTABLISH then
-- connection with supervisor established -- connection with supervisor established
if packet.length == 1 then if _check_length(packet, 1) then
local est_ack = packet.data[1] local est_ack = packet.data[1]
if est_ack == ESTABLISH_ACK.ALLOW then if est_ack == ESTABLISH_ACK.ALLOW then
@@ -453,7 +519,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
self.sv.addr = src_addr self.sv.addr = src_addr
if self.api.linked then if self.api.linked then
iocontrol.report_link_state(LINK_STATE.LINKED) iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, self.api.addr)
else else
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY) iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY)
end end
@@ -474,15 +540,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
end end
self.sv.last_est_ack = est_ack self.sv.last_est_ack = est_ack
else
log.debug("supervisor SCADA_MGMT establish packet length mismatch")
end end
else else
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked") log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
end end
else else _fail_type(packet) end
log.debug("illegal packet type " .. protocol .. " from supervisor", true)
end
else else
log.debug("received packet from unconfigured channel " .. r_chan, true) log.debug("received packet from unconfigured channel " .. r_chan, true)
end end
@@ -500,5 +562,4 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
return public return public
end end
return pocket return pocket

View File

@@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol")
local pocket = require("pocket.pocket") local pocket = require("pocket.pocket")
local renderer = require("pocket.renderer") local renderer = require("pocket.renderer")
local POCKET_VERSION = "v0.7.0-alpha" local POCKET_VERSION = "v0.9.1-alpha"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@@ -31,9 +31,13 @@ if not pocket.load_config() then
-- try to reconfigure (user action) -- try to reconfigure (user action)
local success, error = configure.configure(true) local success, error = configure.configure(true)
if success then if success then
assert(pocket.load_config(), "failed to load valid configuration") if not pocket.load_config() then
println("failed to load a valid configuration, please reconfigure")
return
end
else else
assert(success, "pocket configuration error: " .. error) println("configuration error: " .. error)
return
end end
end end
@@ -50,6 +54,7 @@ log.info("BOOTING pocket.startup " .. POCKET_VERSION)
log.info("========================================") log.info("========================================")
crash.set_env("pocket", POCKET_VERSION) crash.set_env("pocket", POCKET_VERSION)
crash.dbg_log_env()
---------------------------------------- ----------------------------------------
-- main application -- main application
@@ -63,6 +68,9 @@ local function main()
-- mount connected devices -- mount connected devices
ppm.mount_all() ppm.mount_all()
-- record version for GUI
iocontrol.get_db().version = POCKET_VERSION
---------------------------------------- ----------------------------------------
-- setup communications & clocks -- setup communications & clocks
---------------------------------------- ----------------------------------------
@@ -126,7 +134,7 @@ local function main()
-- start connection watchdogs -- start connection watchdogs
conn_wd.sv.feed() conn_wd.sv.feed()
conn_wd.api.feed() conn_wd.api.feed()
log.debug("startup> conn watchdog started") log.debug("startup> conn watchdogs started")
local io_db = iocontrol.get_db() local io_db = iocontrol.get_db()
local nav = io_db.nav local nav = io_db.nav
@@ -144,11 +152,8 @@ local function main()
pocket_comms.link_update() pocket_comms.link_update()
-- update any tasks for the active page -- update any tasks for the active page
if (type(nav.tasks[nav.page]) == "table") then local page_tasks = nav.get_current_page().tasks
for i = 1, #nav.tasks[nav.page] do for i = 1, #page_tasks do page_tasks[i]() end
nav.tasks[nav.page][i]()
end
end
loop_clock.start() loop_clock.start()
elseif conn_wd.sv.is_timer(param1) then elseif conn_wd.sv.is_timer(param1) then

View File

@@ -1,58 +1,39 @@
--
-- Diagnostic Apps
--
local iocontrol = require("pocket.iocontrol") local iocontrol = require("pocket.iocontrol")
local core = require("graphics.core") local core = require("graphics.core")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox") local TextBox = require("graphics.elements.textbox")
local IndicatorLight = require("graphics.elements.indicators.light") local IndicatorLight = require("graphics.elements.indicators.light")
local App = require("graphics.elements.controls.app")
local Checkbox = require("graphics.elements.controls.checkbox") local Checkbox = require("graphics.elements.controls.checkbox")
local PushButton = require("graphics.elements.controls.push_button") local PushButton = require("graphics.elements.controls.push_button")
local SwitchButton = require("graphics.elements.controls.switch_button") local SwitchButton = require("graphics.elements.controls.switch_button")
local cpair = core.cpair local cpair = core.cpair
local NAV_PAGE = iocontrol.NAV_PAGE
local ALIGN = core.ALIGN local ALIGN = core.ALIGN
-- new diagnostics page view -- create diagnostic app pages
---@param root graphics_element parent ---@param root graphics_element parent
local function new_view(root) local function create_pages(root)
local db = iocontrol.get_db() local db = iocontrol.get_db()
local main = Div{parent=root,x=1,y=1}
local diag_home = Div{parent=main,x=1,y=1}
TextBox{parent=diag_home,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER}
local alarm_test = Div{parent=main,x=1,y=1}
local panes = { diag_home, alarm_test }
local page_pane = MultiPane{parent=main,x=1,y=1,panes=panes}
local function navigate_diag()
page_pane.set_value(1)
db.nav.page = NAV_PAGE.DIAG
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.DIAG
end
local function navigate_alarm()
page_pane.set_value(2)
db.nav.page = NAV_PAGE.D_ALARMS
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.D_ALARMS
end
------------------------ ------------------------
-- Alarm Testing Page -- -- Alarm Testing Page --
------------------------ ------------------------
db.nav.register_task(NAV_PAGE.D_ALARMS, db.diag.tone_test.get_tone_states) local alarm_test = Div{parent=root,x=1,y=1}
local alarm_app = db.nav.register_app(iocontrol.APP_ID.ALARMS, alarm_test)
local page = alarm_app.new_page(nil, function () end)
page.tasks = { db.diag.tone_test.get_tone_states }
local ttest = db.diag.tone_test local ttest = db.diag.tone_test
@@ -67,8 +48,6 @@ local function new_view(root)
ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)} ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=navigate_diag}
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)} local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
TextBox{parent=tones,text="Tones",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()} TextBox{parent=tones,text="Tones",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
@@ -132,16 +111,6 @@ local function new_view(root)
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray} local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 } ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
--------------
-- App List --
--------------
App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=navigate_alarm,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
App{parent=diag_home,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
App{parent=diag_home,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)}
return main
end end
return new_view return create_pages

View File

@@ -0,0 +1,26 @@
--
-- Placeholder App
--
local iocontrol = require("pocket.iocontrol")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- create placeholder app page
---@param root graphics_element parent
local function create_pages(root)
local db = iocontrol.get_db()
local main = Div{parent=root,x=1,y=1}
db.nav.register_app(iocontrol.APP_ID.DUMMY, main).new_page(nil, function () end)
TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER}
TextBox{parent=main,text=" pretend something cool is here \x03",x=1,y=10,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors.black)}
end
return create_pages

145
pocket/ui/apps/sys_apps.lua Normal file
View File

@@ -0,0 +1,145 @@
--
-- System Apps
--
local comms = require("scada-common.comms")
local util = require("scada-common.util")
local lockbox = require("lockbox")
local iocontrol = require("pocket.iocontrol")
local pocket = require("pocket.pocket")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local ListBox = require("graphics.elements.listbox")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox")
local PushButton = require("graphics.elements.controls.push_button")
local cpair = core.cpair
local ALIGN = core.ALIGN
-- create system app pages
---@param root graphics_element parent
local function create_pages(root)
local db = iocontrol.get_db()
----------------
-- About Page --
----------------
local about_root = Div{parent=root,x=1,y=1}
local about_app = db.nav.register_app(iocontrol.APP_ID.ABOUT, about_root)
local about_page = about_app.new_page(nil, 1)
local nt_page = about_app.new_page(about_page, 2)
local fw_page = about_app.new_page(about_page, 3)
local hw_page = about_app.new_page(about_page, 4)
local about = Div{parent=about_root,x=1,y=2}
TextBox{parent=about,y=1,text="System Information",height=1,alignment=ALIGN.CENTER}
local btn_fg_bg = cpair(colors.lightBlue, colors.black)
local btn_active = cpair(colors.white, colors.black)
local label = cpair(colors.lightGray, colors.black)
PushButton{parent=about,x=2,y=3,text="Network >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=nt_page.nav_to}
PushButton{parent=about,x=2,y=4,text="Firmware >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fw_page.nav_to}
PushButton{parent=about,x=2,y=5,text="Host Details >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=hw_page.nav_to}
--#region Network Details
local config = pocket.config
local nt_div = Div{parent=about_root,x=1,y=2}
TextBox{parent=nt_div,y=1,text="Network Details",height=1,alignment=ALIGN.CENTER}
PushButton{parent=nt_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
TextBox{parent=nt_div,x=2,y=3,text="Pocket Address",height=1,alignment=ALIGN.LEFT,fg_bg=label}
---@diagnostic disable-next-line: undefined-field
TextBox{parent=nt_div,x=2,text=util.c(os.getComputerID(),":",config.PKT_Channel),height=1,alignment=ALIGN.LEFT}
nt_div.line_break()
TextBox{parent=nt_div,x=2,text="Supervisor Address",height=1,alignment=ALIGN.LEFT,fg_bg=label}
local sv = TextBox{parent=nt_div,x=2,text="",height=1,alignment=ALIGN.LEFT}
nt_div.line_break()
TextBox{parent=nt_div,x=2,text="Coordinator Address",height=1,alignment=ALIGN.LEFT,fg_bg=label}
local coord = TextBox{parent=nt_div,x=2,text="",height=1,alignment=ALIGN.LEFT}
sv.register(db.ps, "sv_addr", function (addr) sv.set_value(util.c(addr, ":", config.SVR_Channel)) end)
coord.register(db.ps, "api_addr", function (addr) coord.set_value(util.c(addr, ":", config.CRD_Channel)) end)
nt_div.line_break()
TextBox{parent=nt_div,x=2,text="Message Authentication",height=1,alignment=ALIGN.LEFT,fg_bg=label}
local auth = util.trinary(type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0, "HMAC-MD5", "None")
TextBox{parent=nt_div,x=2,text=auth,height=1,alignment=ALIGN.LEFT}
--#endregion
--#region Firmware Versions
local fw_div = Div{parent=about_root,x=1,y=2}
TextBox{parent=fw_div,y=1,text="Firmware Versions",height=1,alignment=ALIGN.CENTER}
PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
local fw_list_box = ListBox{parent=fw_div,x=1,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
local fw_list = Div{parent=fw_list_box,x=1,y=2,height=18}
TextBox{parent=fw_list,x=2,text="Pocket Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=fw_list,x=2,text=db.version,height=1,alignment=ALIGN.LEFT}
fw_list.line_break()
TextBox{parent=fw_list,x=2,text="Comms Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=fw_list,x=2,text=comms.version,height=1,alignment=ALIGN.LEFT}
fw_list.line_break()
TextBox{parent=fw_list,x=2,text="API Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=fw_list,x=2,text=comms.api_version,height=1,alignment=ALIGN.LEFT}
fw_list.line_break()
TextBox{parent=fw_list,x=2,text="Common Lib Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=fw_list,x=2,text=util.version,height=1,alignment=ALIGN.LEFT}
fw_list.line_break()
TextBox{parent=fw_list,x=2,text="Graphics Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=fw_list,x=2,text=core.version,height=1,alignment=ALIGN.LEFT}
fw_list.line_break()
TextBox{parent=fw_list,x=2,text="Lockbox Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=fw_list,x=2,text=lockbox.version,height=1,alignment=ALIGN.LEFT}
--#endregion
--#region Host Versions
local hw_div = Div{parent=about_root,x=1,y=2}
TextBox{parent=hw_div,y=1,text="Host Versions",height=1,alignment=ALIGN.CENTER}
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
hw_div.line_break()
TextBox{parent=hw_div,x=2,text="Lua Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=hw_div,x=2,text=_VERSION,height=1,alignment=ALIGN.LEFT}
hw_div.line_break()
TextBox{parent=hw_div,x=2,text="Environment",height=1,alignment=ALIGN.LEFT,fg_bg=label}
TextBox{parent=hw_div,x=2,text=_HOST,height=6,alignment=ALIGN.LEFT}
--#endregion
local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,nt_div,fw_div,hw_div}}
about_app.set_root_pane(root_pane)
end
return create_pages

View File

@@ -4,27 +4,29 @@
local iocontrol = require("pocket.iocontrol") local iocontrol = require("pocket.iocontrol")
local style = require("pocket.ui.style") local diag_apps = require("pocket.ui.apps.diag_apps")
local dummy_app = require("pocket.ui.apps.dummy_app")
local sys_apps = require("pocket.ui.apps.sys_apps")
local conn_waiting = require("pocket.ui.components.conn_waiting") local conn_waiting = require("pocket.ui.components.conn_waiting")
local boiler_page = require("pocket.ui.pages.boiler_page")
local diag_page = require("pocket.ui.pages.diag_page")
local home_page = require("pocket.ui.pages.home_page") local home_page = require("pocket.ui.pages.home_page")
local reactor_page = require("pocket.ui.pages.reactor_page")
local turbine_page = require("pocket.ui.pages.turbine_page")
local unit_page = require("pocket.ui.pages.unit_page") local unit_page = require("pocket.ui.pages.unit_page")
local style = require("pocket.ui.style")
local core = require("graphics.core") local core = require("graphics.core")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
local MultiPane = require("graphics.elements.multipane") local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox") local TextBox = require("graphics.elements.textbox")
local PushButton = require("graphics.elements.controls.push_button")
local Sidebar = require("graphics.elements.controls.sidebar") local Sidebar = require("graphics.elements.controls.sidebar")
local SignalBar = require("graphics.elements.indicators.signal")
local LINK_STATE = iocontrol.LINK_STATE local LINK_STATE = iocontrol.LINK_STATE
local NAV_PAGE = iocontrol.NAV_PAGE
local ALIGN = core.ALIGN local ALIGN = core.ALIGN
@@ -33,26 +35,27 @@ local cpair = core.cpair
-- create new main view -- create new main view
---@param main graphics_element main displaybox ---@param main graphics_element main displaybox
local function init(main) local function init(main)
local nav = iocontrol.get_db().nav local db = iocontrol.get_db()
local ps = iocontrol.get_db().ps
-- window header message -- window header message
TextBox{parent=main,y=1,text="",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} TextBox{parent=main,y=1,text="WIP ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header}
local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
-- db.ps.subscribe("svr_conn_quality", svr_conn.set_value)
-- root panel panes (connection screens + main screen) db.ps.subscribe("crd_conn_quality", crd_conn.set_value)
--
--#region root panel panes (connection screens + main screen)
local root_pane_div = Div{parent=main,x=1,y=2} local root_pane_div = Div{parent=main,x=1,y=2}
local conn_sv_wait = conn_waiting(root_pane_div, 6, false) local conn_sv_wait = conn_waiting(root_pane_div, 6, false)
local conn_api_wait = conn_waiting(root_pane_div, 6, true) local conn_api_wait = conn_waiting(root_pane_div, 6, true)
local main_pane = Div{parent=main,x=1,y=2} local main_pane = Div{parent=main,x=1,y=2}
local root_panes = { conn_sv_wait, conn_api_wait, main_pane }
local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes=root_panes} local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}}
root_pane.register(ps, "link_state", function (state) root_pane.register(db.ps, "link_state", function (state)
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
root_pane.set_value(1) root_pane.set_value(1)
elseif state == LINK_STATE.SV_LINK_ONLY then elseif state == LINK_STATE.SV_LINK_ONLY then
@@ -62,62 +65,29 @@ local function init(main)
end end
end) end)
-- --#endregion
-- main page panel panes & sidebar
-- --#region main page panel panes & sidebar
local page_div = Div{parent=main_pane,x=4,y=1} local page_div = Div{parent=main_pane,x=4,y=1}
local sidebar_tabs = { home_page(page_div)
{ unit_page(page_div)
char = "#",
color = cpair(colors.black,colors.green)
},
{
char = "U",
color = cpair(colors.black,colors.yellow)
},
{
char = "R",
color = cpair(colors.black,colors.cyan)
},
{
char = "B",
color = cpair(colors.black,colors.lightGray)
},
{
char = "T",
color = cpair(colors.black,colors.white)
},
{
char = "D",
color = cpair(colors.black,colors.orange)
}
}
local panes = { home_page(page_div), unit_page(page_div), reactor_page(page_div), boiler_page(page_div), turbine_page(page_div), diag_page(page_div) } diag_apps(page_div)
sys_apps(page_div)
dummy_app(page_div)
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered")
local function navigate_sidebar(page) db.nav.set_pane(MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()})
if page == 1 then db.nav.set_sidebar(Sidebar{parent=main_pane,x=1,y=1,height=18,fg_bg=cpair(colors.white,colors.gray)})
nav.page = nav.sub_pages[NAV_PAGE.HOME]
elseif page == 2 then
nav.page = nav.sub_pages[NAV_PAGE.UNITS]
elseif page == 3 then
nav.page = nav.sub_pages[NAV_PAGE.REACTORS]
elseif page == 4 then
nav.page = nav.sub_pages[NAV_PAGE.BOILERS]
elseif page == 5 then
nav.page = nav.sub_pages[NAV_PAGE.TURBINES]
elseif page == 6 then
nav.page = nav.sub_pages[NAV_PAGE.DIAG]
end
page_pane.set_value(page) PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up}
end
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=navigate_sidebar} db.nav.open_app(iocontrol.APP_ID.ROOT)
--#endregion
end end
return init return init

View File

@@ -1,22 +0,0 @@
-- local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.cpair
local ALIGN = core.ALIGN
-- new boiler page view
---@param root graphics_element parent
local function new_view(root)
local main = Div{parent=root,x=1,y=1}
TextBox{parent=main,text="BOILERS",x=1,y=1,height=1,alignment=ALIGN.CENTER}
return main
end
return new_view

View File

@@ -1,21 +1,64 @@
local core = require("graphics.core") --
-- Main Home Page
--
local Div = require("graphics.elements.div") local iocontrol = require("pocket.iocontrol")
local App = require("graphics.elements.controls.app") local core = require("graphics.core")
local AppMultiPane = require("graphics.elements.appmultipane")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local App = require("graphics.elements.controls.app")
local cpair = core.cpair local cpair = core.cpair
local APP_ID = iocontrol.APP_ID
local ALIGN = core.ALIGN
-- new home page view -- new home page view
---@param root graphics_element parent ---@param root graphics_element parent
local function new_view(root) local function new_view(root)
local main = Div{parent=root,x=1,y=1} local db = iocontrol.get_db()
App{parent=main,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)} local main = Div{parent=root,x=1,y=1,height=19}
App{parent=main,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)}
App{parent=main,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)} local app = db.nav.register_app(iocontrol.APP_ID.ROOT, main)
App{parent=main,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)}
App{parent=main,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} local apps_1 = Div{parent=main,x=1,y=1,height=15}
local apps_2 = Div{parent=main,x=1,y=1,height=15}
local panes = { apps_1, apps_2 }
local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=app.switcher}
app.set_root_pane(app_pane)
app.new_page(app.new_page(nil, 1), 2)
local function open(id) db.nav.open_app(id) end
app.set_sidebar({
{ label = " #\x10", tall = true, color = core.cpair(colors.black, colors.green), callback = function () open(APP_ID.ROOT) end }
})
local active_fg_bg = cpair(colors.white,colors.gray)
App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
App{parent=apps_1,x=9,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER}
App{parent=apps_2,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
App{parent=apps_2,x=9,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
App{parent=apps_2,x=16,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
return main return main
end end

View File

@@ -1,22 +0,0 @@
-- local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.cpair
local ALIGN = core.ALIGN
-- new reactor page view
---@param root graphics_element parent
local function new_view(root)
local main = Div{parent=root,x=1,y=1}
TextBox{parent=main,text="REACTOR",x=1,y=1,height=1,alignment=ALIGN.CENTER}
return main
end
return new_view

View File

@@ -1,22 +0,0 @@
-- local style = require("pocket.ui.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.cpair
local ALIGN = core.ALIGN
-- new turbine page view
---@param root graphics_element parent
local function new_view(root)
local main = Div{parent=root,x=1,y=1}
TextBox{parent=main,text="TURBINES",x=1,y=1,height=1,alignment=ALIGN.CENTER}
return main
end
return new_view

View File

@@ -1,20 +1,319 @@
-- local style = require("pocket.ui.style") --
-- Unit Overview Page
--
local core = require("graphics.core") local util = require("scada-common.util")
-- local log = require("scada-common.log")
local Div = require("graphics.elements.div") local iocontrol = require("pocket.iocontrol")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.cpair local core = require("graphics.core")
local Div = require("graphics.elements.div")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data")
local IconIndicator = require("graphics.elements.indicators.icon")
-- local RadIndicator = require("graphics.elements.indicators.rad")
-- local VerticalBar = require("graphics.elements.indicators.vbar")
local PushButton = require("graphics.elements.controls.push_button")
local ALIGN = core.ALIGN local ALIGN = core.ALIGN
local cpair = core.cpair
local basic_states = {
{ color = cpair(colors.black, colors.lightGray), symbol = "\x07" },
{ color = cpair(colors.black, colors.red), symbol = "-" },
{ color = cpair(colors.black, colors.yellow), symbol = "\x1e" },
{ color = cpair(colors.black, colors.green), symbol = "+" }
}
local mode_states = {
{ color = cpair(colors.black, colors.lightGray), symbol = "\x07" },
{ color = cpair(colors.black, colors.red), symbol = "-" },
{ color = cpair(colors.black, colors.green), symbol = "+" },
{ color = cpair(colors.black, colors.purple), symbol = "A" }
}
local emc_ind_s = {
{ color = cpair(colors.black, colors.gray), symbol = "-" },
{ color = cpair(colors.black, colors.white), symbol = "\x07" },
{ color = cpair(colors.black, colors.green), symbol = "+" }
}
local red_ind_s = {
{ color = cpair(colors.black, colors.lightGray), symbol = "+" },
{ color = cpair(colors.black, colors.red), symbol = "-" }
}
local yel_ind_s = {
{ color = cpair(colors.black, colors.lightGray), symbol = "+" },
{ color = cpair(colors.black, colors.yellow), symbol = "-" }
}
-- new unit page view -- new unit page view
---@param root graphics_element parent ---@param root graphics_element parent
local function new_view(root) local function new_view(root)
local db = iocontrol.get_db()
local main = Div{parent=root,x=1,y=1} local main = Div{parent=root,x=1,y=1}
TextBox{parent=main,text="UNITS",x=1,y=1,height=1,alignment=ALIGN.CENTER} local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main)
TextBox{parent=main,y=2,text="Units App",height=1,alignment=ALIGN.CENTER}
TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER}
local btn_fg_bg = cpair(colors.yellow, colors.black)
local btn_active = cpair(colors.white, colors.black)
-- local label = cpair(colors.lightGray, colors.black)
local nav_links = {}
local function set_sidebar(id)
-- local unit = db.units[id] ---@type pioctl_unit
local list = {
{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end },
{ label = "U-" .. id, color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(id) end },
{ label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = nav_links[id].alarm },
{ label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps },
-- { label = " R ", color = core.cpair(colors.black, colors.lightGray), callback = function () end },
{ label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = nav_links[id].rcs },
}
-- for i = 1, unit.num_boilers do
-- table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightBlue), callback = function () end })
-- end
-- for i = 1, unit.num_turbines do
-- table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.white), callback = function () end })
-- end
app.set_sidebar(list)
end
local function load()
local page_div = Div{parent=main,x=2,y=2,width=main.get_width()-2}
local panes = {}
local active_unit = 1
-- create all page divs
for _ = 1, db.facility.num_units do
local div = Div{parent=page_div}
table.insert(panes, div)
table.insert(nav_links, {})
end
-- previous unit
local function prev(x)
active_unit = util.trinary(x == 1, db.facility.num_units, x - 1)
app.switcher(active_unit)
set_sidebar(active_unit)
end
-- next unit
local function next(x)
active_unit = util.trinary(x == db.facility.num_units, 1, x + 1)
app.switcher(active_unit)
set_sidebar(active_unit)
end
for i = 1, db.facility.num_units do
local u_div = panes[i] ---@type graphics_element
local unit = db.units[i] ---@type pioctl_unit
local u_ps = unit.unit_ps
-- refresh data callback, every 500ms it will re-send the query
local last_update = 0
local function update()
if util.time_ms() - last_update >= 500 then
db.api.get_unit(i)
last_update = util.time_ms()
end
end
--#region Main Unit Overview
local u_page = app.new_page(nil, i)
u_page.tasks = { update }
TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,height=1,alignment=ALIGN.CENTER}
PushButton{parent=u_div,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()prev(i)end}
PushButton{parent=u_div,x=21,y=1,text=">",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()next(i)end}
local type = util.trinary(unit.num_boilers > 0, "Sodium Cooled Reactor", "Boiling Water Reactor")
TextBox{parent=u_div,y=3,text=type,height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)}
local lu_col = cpair(colors.lightGray, colors.lightGray)
local text_fg = cpair(colors.white, colors._INHERIT)
local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Rate",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit="K",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg}
local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states}
rate.register(u_ps, "act_burn_rate", rate.update)
temp.register(u_ps, "temp", temp.update)
ctrl.register(u_ps, "U_ControlStatus", ctrl.update)
u_div.line_break()
local rct = IconIndicator{parent=u_div,x=1,label="Fission Reactor",states=basic_states}
local rps = IconIndicator{parent=u_div,x=1,label="Protection System",states=basic_states}
rct.register(u_ps, "U_ReactorStatus", rct.update)
rps.register(u_ps, "U_RPS", rps.update)
u_div.line_break()
local rcs = IconIndicator{parent=u_div,x=1,label="Coolant System",states=basic_states}
rcs.register(u_ps, "U_RCS", rcs.update)
for b = 1, unit.num_boilers do
local blr = IconIndicator{parent=u_div,x=1,label="Boiler "..b,states=basic_states}
blr.register(unit.boiler_ps_tbl[b], "BoilerStatus", blr.update)
end
for t = 1, unit.num_turbines do
local tbn = IconIndicator{parent=u_div,x=1,label="Turbine "..t,states=basic_states}
tbn.register(unit.turbine_ps_tbl[t], "TurbineStatus", tbn.update)
end
--#endregion
--#region Alarms Tab
local alm_div = Div{parent=page_div}
table.insert(panes, alm_div)
local alm_page = app.new_page(u_page, #panes)
alm_page.tasks = { update }
nav_links[i].alarm = alm_page.nav_to
TextBox{parent=alm_div,y=1,text="Unit Alarms",height=1,alignment=ALIGN.CENTER}
TextBox{parent=alm_div,y=3,text="work in progress",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)}
--#endregion
--#region RPS Tab
local rps_div = Div{parent=page_div}
table.insert(panes, rps_div)
local rps_page = app.new_page(u_page, #panes)
rps_page.tasks = { update }
nav_links[i].rps = rps_page.nav_to
TextBox{parent=rps_div,y=1,text="Protection System",height=1,alignment=ALIGN.CENTER}
local r_trip = IconIndicator{parent=rps_div,y=3,label="RPS Trip",states=basic_states}
r_trip.register(u_ps, "U_RPS", r_trip.update)
local r_mscrm = IconIndicator{parent=rps_div,y=5,label="Manual SCRAM",states=red_ind_s}
local r_ascrm = IconIndicator{parent=rps_div,label="Automatic SCRAM",states=red_ind_s}
local rps_tmo = IconIndicator{parent=rps_div,label="Timeout",states=yel_ind_s}
local rps_flt = IconIndicator{parent=rps_div,label="PPM Fault",states=yel_ind_s}
local rps_sfl = IconIndicator{parent=rps_div,label="Not Formed",states=red_ind_s}
r_mscrm.register(u_ps, "manual", r_mscrm.update)
r_ascrm.register(u_ps, "automatic", r_ascrm.update)
rps_tmo.register(u_ps, "timeout", rps_tmo.update)
rps_flt.register(u_ps, "fault", rps_flt.update)
rps_sfl.register(u_ps, "sys_fail", rps_sfl.update)
rps_div.line_break()
local rps_dmg = IconIndicator{parent=rps_div,label="Reactor Damage Hi",states=red_ind_s}
local rps_tmp = IconIndicator{parent=rps_div,label="Temp. Critical",states=red_ind_s}
local rps_nof = IconIndicator{parent=rps_div,label="Fuel Level Lo",states=yel_ind_s}
local rps_exw = IconIndicator{parent=rps_div,label="Waste Level Hi",states=yel_ind_s}
local rps_loc = IconIndicator{parent=rps_div,label="Coolant Lo Lo",states=yel_ind_s}
local rps_exh = IconIndicator{parent=rps_div,label="Heated Coolant Hi",states=yel_ind_s}
rps_dmg.register(u_ps, "high_dmg", rps_dmg.update)
rps_tmp.register(u_ps, "high_temp", rps_tmp.update)
rps_nof.register(u_ps, "no_fuel", rps_nof.update)
rps_exw.register(u_ps, "ex_waste", rps_exw.update)
rps_loc.register(u_ps, "low_cool", rps_loc.update)
rps_exh.register(u_ps, "ex_hcool", rps_exh.update)
--#endregion
--#region RCS Tab
local rcs_div = Div{parent=page_div}
table.insert(panes, rcs_div)
local rcs_page = app.new_page(u_page, #panes)
rcs_page.tasks = { update }
nav_links[i].rcs = rcs_page.nav_to
TextBox{parent=rcs_div,y=1,text="Coolant System",height=1,alignment=ALIGN.CENTER}
local r_rtrip = IconIndicator{parent=rcs_div,y=3,label="RCP Trip",states=red_ind_s}
local r_cflow = IconIndicator{parent=rcs_div,label="RCS Flow Lo",states=yel_ind_s}
local r_clow = IconIndicator{parent=rcs_div,label="Coolant Level Lo",states=yel_ind_s}
r_rtrip.register(u_ps, "RCPTrip", r_rtrip.update)
r_cflow.register(u_ps, "RCSFlowLow", r_cflow.update)
r_clow.register(u_ps, "CoolantLevelLow", r_clow.update)
local c_flt = IconIndicator{parent=rcs_div,label="RCS HW Fault",states=yel_ind_s}
local c_emg = IconIndicator{parent=rcs_div,label="Emergency Coolant",states=emc_ind_s}
local c_mwrf = IconIndicator{parent=rcs_div,label="Max Water Return",states=yel_ind_s}
c_flt.register(u_ps, "RCSFault", c_flt.update)
c_emg.register(u_ps, "EmergencyCoolant", c_emg.update)
c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update)
-- rcs_div.line_break()
-- TextBox{parent=rcs_div,text="Mismatches",height=1,alignment=ALIGN.CENTER,fg_bg=label}
local c_cfm = IconIndicator{parent=rcs_div,label="Coolant Feed",states=yel_ind_s}
local c_brm = IconIndicator{parent=rcs_div,label="Boil Rate",states=yel_ind_s}
local c_sfm = IconIndicator{parent=rcs_div,label="Steam Feed",states=yel_ind_s}
c_cfm.register(u_ps, "CoolantFeedMismatch", c_cfm.update)
c_brm.register(u_ps, "BoilRateMismatch", c_brm.update)
c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update)
rcs_div.line_break()
-- TextBox{parent=rcs_div,text="Aggregate Checks",height=1,alignment=ALIGN.CENTER,fg_bg=label}
if unit.num_boilers > 0 then
local wll = IconIndicator{parent=rcs_div,label="Boiler Water Lo",states=red_ind_s}
local hrl = IconIndicator{parent=rcs_div,label="Heating Rate Lo",states=yel_ind_s}
wll.register(u_ps, "U_WaterLevelLow", wll.update)
hrl.register(u_ps, "U_HeatingRateLow", hrl.update)
end
local tospd = IconIndicator{parent=rcs_div,label="TRB Over Speed",states=red_ind_s}
local gtrip = IconIndicator{parent=rcs_div,label="Generator Trip",states=yel_ind_s}
local ttrip = IconIndicator{parent=rcs_div,label="Turbine Trip",states=red_ind_s}
tospd.register(u_ps, "U_TurbineOverSpeed", tospd.update)
gtrip.register(u_ps, "U_GeneratorTrip", gtrip.update)
ttrip.register(u_ps, "U_TurbineTrip", ttrip.update)
--#endregion
end
-- setup multipane
local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
app.set_root_pane(u_pane)
set_sidebar(active_unit)
end
app.set_on_load(load)
return main return main
end end

View File

@@ -8,6 +8,7 @@ local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
local themes = require("graphics.themes")
local DisplayBox = require("graphics.elements.displaybox") local DisplayBox = require("graphics.elements.displaybox")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
@@ -23,6 +24,8 @@ local RadioButton = require("graphics.elements.controls.radio_button")
local NumberField = require("graphics.elements.form.number_field") local NumberField = require("graphics.elements.form.number_field")
local TextField = require("graphics.elements.form.text_field") local TextField = require("graphics.elements.form.text_field")
local IndLight = require("graphics.elements.indicators.light")
local println = util.println local println = util.println
local tri = util.trinary local tri = util.trinary
@@ -34,8 +37,10 @@ local RIGHT = core.ALIGN.RIGHT
-- changes to the config data/format to let the user know -- changes to the config data/format to let the user know
local changes = { local changes = {
{"v1.6.2", { "AuthKey minimum length is now 8 (if set)" } }, { "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } },
{"v1.6.8", { "ConnTimeout can now have a fractional part" } } { "v1.6.8", { "ConnTimeout can now have a fractional part" } },
{ "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } },
{ "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
} }
---@class plc_configurator ---@class plc_configurator
@@ -46,34 +51,25 @@ local style = {}
style.root = cpair(colors.black, colors.lightGray) style.root = cpair(colors.black, colors.lightGray)
style.header = cpair(colors.white, colors.gray) style.header = cpair(colors.white, colors.gray)
style.colors = { style.colors = themes.smooth_stone.colors
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.lime, hex = 0x80ff80 },
{ c = colors.green, hex = 0x4aee8a },
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee },
{ c = colors.pink, hex = 0xf26ba2 },
{ c = colors.magenta, hex = 0xf9488a },
{ c = colors.lightGray, hex = 0xcacaca },
{ c = colors.gray, hex = 0x575757 }
}
local bw_fg_bg = cpair(colors.black, colors.white) local bw_fg_bg = cpair(colors.black, colors.white)
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray) local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
local nav_fg_bg = bw_fg_bg local nav_fg_bg = bw_fg_bg
local btn_act_fg_bg = cpair(colors.white, colors.gray) local btn_act_fg_bg = cpair(colors.white, colors.gray)
---@class _plc_cfg_tool_ctl
local tool_ctl = { local tool_ctl = {
ask_config = false, ask_config = false,
has_config = false, has_config = false,
viewing_config = false, viewing_config = false,
importing_legacy = false, importing_legacy = false,
jumped_to_color = false,
view_cfg = nil, ---@type graphics_element view_cfg = nil, ---@type graphics_element
color_cfg = nil, ---@type graphics_element
color_next = nil, ---@type graphics_element
color_apply = nil, ---@type graphics_element
settings_apply = nil, ---@type graphics_element settings_apply = nil, ---@type graphics_element
set_networked = nil, ---@type function set_networked = nil, ---@type function
@@ -103,6 +99,8 @@ local tmp_cfg = {
LogMode = 0, LogMode = 0,
LogPath = "", LogPath = "",
LogDebug = false, LogDebug = false,
FrontPanelTheme = 1,
ColorMode = 1
} }
---@class plc_config ---@class plc_config
@@ -124,7 +122,9 @@ local fields = {
{ "AuthKey", "Facility Auth Key" , ""}, { "AuthKey", "Facility Auth Key" , ""},
{ "LogMode", "Log Mode", log.MODE.APPEND }, { "LogMode", "Log Mode", log.MODE.APPEND },
{ "LogPath", "Log Path", "/log.txt" }, { "LogPath", "Log Path", "/log.txt" },
{ "LogDebug","Log Debug Messages", false } { "LogDebug", "Log Debug Messages", false },
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
} }
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" } local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
@@ -176,10 +176,11 @@ local function config_view(display)
local plc_cfg = Div{parent=root_pane_div,x=1,y=1} local plc_cfg = Div{parent=root_pane_div,x=1,y=1}
local net_cfg = Div{parent=root_pane_div,x=1,y=1} local net_cfg = Div{parent=root_pane_div,x=1,y=1}
local log_cfg = Div{parent=root_pane_div,x=1,y=1} local log_cfg = Div{parent=root_pane_div,x=1,y=1}
local clr_cfg = Div{parent=root_pane_div,x=1,y=1}
local summary = Div{parent=root_pane_div,x=1,y=1} local summary = Div{parent=root_pane_div,x=1,y=1}
local changelog = Div{parent=root_pane_div,x=1,y=1} local changelog = Div{parent=root_pane_div,x=1,y=1}
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,plc_cfg,net_cfg,log_cfg,summary,changelog}} local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,plc_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog}}
-- Main Page -- Main Page
@@ -196,7 +197,7 @@ local function config_view(display)
tool_ctl.viewing_config = true tool_ctl.viewing_config = true
tool_ctl.gen_summary(settings_cfg) tool_ctl.gen_summary(settings_cfg)
tool_ctl.settings_apply.hide(true) tool_ctl.settings_apply.hide(true)
main_pane.set_value(5) main_pane.set_value(6)
end end
if fs.exists("/reactor-plc/config.lua") then if fs.exists("/reactor-plc/config.lua") then
@@ -207,10 +208,21 @@ local function config_view(display)
PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end local function jump_color()
tool_ctl.jumped_to_color = true
tool_ctl.color_next.hide(true)
tool_ctl.color_apply.show()
main_pane.set_value(5)
end
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
if not tool_ctl.has_config then
tool_ctl.view_cfg.disable()
tool_ctl.color_cfg.disable()
end
--#region PLC --#region PLC
@@ -419,10 +431,8 @@ local function config_view(display)
tmp_cfg.LogMode = mode.get_value() - 1 tmp_cfg.LogMode = mode.get_value() - 1
tmp_cfg.LogPath = path.get_value() tmp_cfg.LogPath = path.get_value()
tmp_cfg.LogDebug = en_dbg.get_value() tmp_cfg.LogDebug = en_dbg.get_value()
tool_ctl.gen_summary(tmp_cfg) tool_ctl.color_apply.hide(true)
tool_ctl.viewing_config = false tool_ctl.color_next.show()
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(5) main_pane.set_value(5)
else path_err.show() end else path_err.show() end
end end
@@ -436,6 +446,116 @@ local function config_view(display)
--#endregion --#endregion
--#region Color Options
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."}
TextBox{parent=clr_c_2,x=21,y=7,height=1,text="Preview"}
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
local function recolor(value)
local c = themes.smooth_stone.color_modes[value]
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
b_off.hide()
g_off.show()
else
g_off.hide()
b_off.show()
end
if #c == 0 then
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
else
term.setPaletteColor(colors.green, c[1].hex)
term.setPaletteColor(colors.yellow, c[2].hex)
term.setPaletteColor(colors.red, c[3].hex)
end
end
TextBox{parent=clr_c_2,x=1,y=7,height=1,width=10,text="Color Mode"}
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local function back_from_colors()
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4))
tool_ctl.jumped_to_color = false
recolor(1)
end
local function show_access()
clr_pane.set_value(2)
recolor(c_mode.get_value())
end
local function submit_colors()
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
tmp_cfg.ColorMode = c_mode.get_value()
if tool_ctl.jumped_to_color then
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
settings.set("ColorMode", tmp_cfg.ColorMode)
if settings.save("/reactor-plc.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
clr_pane.set_value(3)
else
clr_pane.set_value(4)
end
else
tool_ctl.gen_summary(tmp_cfg)
tool_ctl.viewing_config = false
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(6)
end
end
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply.hide(true)
local function c_go_home()
main_pane.set_value(1)
clr_pane.set_value(1)
end
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Summary and Saving --#region Summary and Saving
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49} local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
@@ -456,7 +576,7 @@ local function config_view(display)
tool_ctl.importing_legacy = false tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show() tool_ctl.settings_apply.show()
else else
main_pane.set_value(4) main_pane.set_value(5)
end end
end end
@@ -487,6 +607,8 @@ local function config_view(display)
try_set(mode, ini_cfg.LogMode) try_set(mode, ini_cfg.LogMode)
try_set(path, ini_cfg.LogPath) try_set(path, ini_cfg.LogPath)
try_set(en_dbg, ini_cfg.LogDebug) try_set(en_dbg, ini_cfg.LogDebug)
try_set(fp_theme, ini_cfg.FrontPanelTheme)
try_set(c_mode, ini_cfg.ColorMode)
tool_ctl.view_cfg.enable() tool_ctl.view_cfg.enable()
@@ -511,6 +633,7 @@ local function config_view(display)
main_pane.set_value(1) main_pane.set_value(1)
plc_pane.set_value(1) plc_pane.set_value(1)
net_pane.set_value(1) net_pane.set_value(1)
clr_pane.set_value(1)
sum_pane.set_value(1) sum_pane.set_value(1)
end end
@@ -588,7 +711,7 @@ local function config_view(display)
tool_ctl.gen_summary(tmp_cfg) tool_ctl.gen_summary(tmp_cfg)
sum_pane.set_value(1) sum_pane.set_value(1)
main_pane.set_value(5) main_pane.set_value(6)
tool_ctl.importing_legacy = true tool_ctl.importing_legacy = true
end end
@@ -606,7 +729,7 @@ local function config_view(display)
local alternate = false local alternate = false
local inner_width = setting_list.get_width() - 1 local inner_width = setting_list.get_width() - 1
tool_ctl.show_key_btn.enable() if cfg.AuthKey then tool_ctl.show_key_btn.enable() else tool_ctl.show_key_btn.disable() end
tool_ctl.auth_key_value = cfg.AuthKey or "" -- to show auth key tool_ctl.auth_key_value = cfg.AuthKey or "" -- to show auth key
for i = 1, #fields do for i = 1, #fields do
@@ -617,9 +740,15 @@ local function config_view(display)
local raw = cfg[f[1]] local raw = cfg[f[1]]
local val = util.strval(raw) local val = util.strval(raw)
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end if f[1] == "AuthKey" and raw then val = string.rep("*", string.len(val))
if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
if f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw) end elseif f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw)
elseif f[1] == "FrontPanelTheme" then
val = util.strval(themes.fp_theme_name(raw))
elseif f[1] == "ColorMode" then
val = util.strval(themes.color_mode_name(raw))
end
if val == "nil" then val = "<not set>" end if val == "nil" then val = "<not set>" end
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))

View File

@@ -70,9 +70,9 @@ function databus.tx_link_state(state)
end end
-- transmit reactor enable state across the bus -- transmit reactor enable state across the bus
---@param active boolean reactor active ---@param active any reactor active
function databus.tx_reactor_state(active) function databus.tx_reactor_state(active)
databus.ps.publish("reactor_active", active) databus.ps.publish("reactor_active", active == true)
end end
-- transmit RPS data across the bus -- transmit RPS data across the bus

View File

@@ -23,6 +23,8 @@ local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair") local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb") local RGBLED = require("graphics.elements.indicators.ledrgb")
local LINK_STATE = types.PANEL_LINK_STATE
local ALIGN = core.ALIGN local ALIGN = core.ALIGN
local cpair = core.cpair local cpair = core.cpair
@@ -34,8 +36,12 @@ local ind_red = style.ind_red
-- create new front panel view -- create new front panel view
---@param panel graphics_element main displaybox ---@param panel graphics_element main displaybox
local function init(panel) local function init(panel)
local header = TextBox{parent=panel,y=1,text="REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} local s_hi_box = style.theme.highlight_box
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("REACTOR PLC - UNIT ", id)) end)
local disabled_fg = style.fp.disabled_fg
local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end)
-- --
-- system indicators -- system indicators
@@ -52,13 +58,47 @@ local function init(panel)
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green} local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
local modem = LED{parent=system,label="MODEM",colors=ind_grn} local modem = LED{parent=system,label="MODEM",colors=ind_grn}
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
network.update(types.PANEL_LINK_STATE.DISCONNECTED) if not style.colorblind then
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
network.register(databus.ps, "link_state", network.update)
else
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.ind_bkg,c1=colors.red,c2=colors.green}
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.ind_bkg,c1=colors.red,c2=colors.green}
local nt_col = LED{parent=system,label="NT COLLISION",colors=ind_red}
nt_lnk.register(databus.ps, "link_state", function (state)
local value = 2
if state == LINK_STATE.DISCONNECTED then
value = 1
elseif state == LINK_STATE.LINKED then
value = 3
end
nt_lnk.update(value)
end)
nt_ver.register(databus.ps, "link_state", function (state)
local value = 3
if state == LINK_STATE.BAD_VERSION then
value = 2
elseif state == LINK_STATE.DISCONNECTED then
value = 1
end
nt_ver.update(value)
end)
nt_col.register(databus.ps, "link_state", function (state) nt_col.update(state == LINK_STATE.COLLISION) end)
end
system.line_break() system.line_break()
reactor.register(databus.ps, "reactor_dev_state", reactor.update) reactor.register(databus.ps, "reactor_dev_state", reactor.update)
modem.register(databus.ps, "has_modem", modem.update) modem.register(databus.ps, "has_modem", modem.update)
network.register(databus.ps, "link_state", network.update)
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn} local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
local rt_rps = LED{parent=system,label="RT RPS",colors=ind_grn} local rt_rps = LED{parent=system,label="RT RPS",colors=ind_grn}
@@ -75,7 +115,7 @@ local function init(panel)
---@diagnostic disable-next-line: undefined-field ---@diagnostic disable-next-line: undefined-field
local comp_id = util.sprintf("(%d)", os.getComputerID()) local comp_id = util.sprintf("(%d)", os.getComputerID())
TextBox{parent=system,x=9,y=5,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)} TextBox{parent=system,x=9,y=5,width=6,height=1,text=comp_id,fg_bg=disabled_fg}
-- --
-- status & controls -- status & controls
@@ -91,12 +131,12 @@ local function init(panel)
emer_cool.register(databus.ps, "emer_cool", emer_cool.update) emer_cool.register(databus.ps, "emer_cool", emer_cool.update)
end end
local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,colors.lightGray,true),even_inner=true,fg_bg=cpair(colors.black,colors.ivory)} local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=cpair(colors.black,colors.lightGray)} local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=s_hi_box}
local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=ind_red,flash=true,period=flasher.PERIOD.BLINK_250_MS} local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=ind_red,flash=true,period=flasher.PERIOD.BLINK_250_MS}
local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,border=border(1,colors.white,true),even_inner=true,fg_bg=cpair(colors.black,colors.ivory)} local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true}
local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=cpair(colors.black,colors.white)} local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=s_hi_box}
PushButton{parent=controls,x=1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)} PushButton{parent=controls,x=1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)}
PushButton{parent=controls,x=9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)} PushButton{parent=controls,x=9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)}
@@ -107,9 +147,9 @@ local function init(panel)
-- about footer -- about footer
-- --
local about = Rectangle{parent=panel,width=32,height=3,x=2,y=16,border=border(1,colors.ivory),thin=true,fg_bg=cpair(colors.black,colors.white)} local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
local fw_v = TextBox{parent=about,x=2,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1} local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
local comms_v = TextBox{parent=about,x=17,y=1,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1} local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
@@ -118,7 +158,7 @@ local function init(panel)
-- rps list -- rps list
-- --
local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,colors.lightGray),thin=true,fg_bg=cpair(colors.black,colors.lightGray)} local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box}
local rps_man = LED{parent=rps,label="MANUAL",colors=ind_red} local rps_man = LED{parent=rps,label="MANUAL",colors=ind_red}
local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=ind_red} local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=ind_red}
local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=ind_red} local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=ind_red}

View File

@@ -2,46 +2,40 @@
-- Graphics Style Options -- Graphics Style Options
-- --
local core = require("graphics.core") local core = require("graphics.core")
local themes = require("graphics.themes")
---@class plc_style
local style = {} local style = {}
local cpair = core.cpair local cpair = core.cpair
-- GLOBAL -- style.theme = themes.sandstone
style.fp = themes.get_fp_style(style.theme)
-- remap global colors style.colorblind = false
colors.ivory = colors.pink
colors.yellow_hc = colors.purple
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
style.root = cpair(colors.black, colors.ivory)
style.header = cpair(colors.black, colors.lightGray)
style.colors = {
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
-- { c = colors.white, hex = 0xdcd9ca },
{ c = colors.lightGray, hex = 0xb1b8b3 },
{ c = colors.gray, hex = 0x575757 },
-- { c = colors.black, hex = 0x191919 },
{ c = colors.brown, hex = 0x672223 } -- RED OFF
}
-- COMMON COLOR PAIRS --
style.ind_grn = cpair(colors.green, colors.green_off) style.ind_grn = cpair(colors.green, colors.green_off)
style.ind_red = cpair(colors.red, colors.red_off) style.ind_red = cpair(colors.red, colors.red_off)
-- set theme per configuration
---@param fp FP_THEME front panel theme
---@param color_mode COLOR_MODE the color mode to use
function style.set_theme(fp, color_mode)
if fp == themes.FP_THEME.SANDSTONE then
style.theme = themes.sandstone
elseif fp == themes.FP_THEME.BASALT then
style.theme = themes.basalt
end
style.fp = themes.get_fp_style(style.theme)
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
if color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND then
style.ind_bkg = colors.gray
else
style.ind_bkg = colors.black
end
end
return style return style

View File

@@ -6,6 +6,8 @@ local rsio = require("scada-common.rsio")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local themes = require("graphics.themes")
local databus = require("reactor-plc.databus") local databus = require("reactor-plc.databus")
local plc = {} local plc = {}
@@ -23,8 +25,8 @@ local RPS_LIMITS = const.RPS_LIMITS
-- I sure hope the devs don't change this error message, not that it would have safety implications -- I sure hope the devs don't change this error message, not that it would have safety implications
-- I wish they didn't change it to be like this -- I wish they didn't change it to be like this
local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." local PCALL_SCRAM_MSG = "Scram requires the reactor to be active."
local PCALL_START_MSG = "pcall: Reactor is already active." local PCALL_START_MSG = "Reactor is already active."
---@type plc_config ---@type plc_config
local config = {} local config = {}
@@ -52,6 +54,9 @@ function plc.load_config()
config.LogPath = settings.get("LogPath") config.LogPath = settings.get("LogPath")
config.LogDebug = settings.get("LogDebug") config.LogDebug = settings.get("LogDebug")
config.FrontPanelTheme = settings.get("FrontPanelTheme")
config.ColorMode = settings.get("ColorMode")
local cfv = util.new_validator() local cfv = util.new_validator()
cfv.assert_type_bool(config.Networked) cfv.assert_type_bool(config.Networked)
@@ -69,7 +74,7 @@ function plc.load_config()
if type(config.AuthKey) == "string" then if type(config.AuthKey) == "string" then
local len = string.len(config.AuthKey) local len = string.len(config.AuthKey)
cfv.assert_eq(len == 0 or len >= 8, true) cfv.assert(len == 0 or len >= 8)
end end
end end
@@ -78,6 +83,11 @@ function plc.load_config()
cfv.assert_type_str(config.LogPath) cfv.assert_type_str(config.LogPath)
cfv.assert_type_bool(config.LogDebug) cfv.assert_type_bool(config.LogDebug)
cfv.assert_type_int(config.FrontPanelTheme)
cfv.assert_range(config.FrontPanelTheme, 1, 2)
cfv.assert_type_int(config.ColorMode)
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
-- check emergency coolant configuration if enabled -- check emergency coolant configuration if enabled
if config.EmerCoolEnable then if config.EmerCoolEnable then
cfv.assert_eq(rsio.is_valid_side(config.EmerCoolSide), true) cfv.assert_eq(rsio.is_valid_side(config.EmerCoolSide), true)
@@ -129,6 +139,21 @@ function plc.rps_init(reactor, is_formed)
end end
end end
-- check if the result of a peripheral call was OK, handle the failure if not
---@nodiscard
---@param result any PPM function call result
---@return boolean succeeded if the result is OK, false if it was a PPM failure
local function _check_and_handle_ppm_call(result)
if result == ppm.ACCESS_FAULT then
_set_fault()
-- if undefined, then the reactor isn't formed
if reactor.__p_last_fault() == ppm.UNDEFINED_FIELD then self.formed = false end
else return true end
return false
end
-- set emergency coolant control (if configured) -- set emergency coolant control (if configured)
---@param state boolean true to enable emergency coolant, false to disable ---@param state boolean true to enable emergency coolant, false to disable
local function _set_emer_cool(state) local function _set_emer_cool(state)
@@ -167,25 +192,20 @@ function plc.rps_init(reactor, is_formed)
-- check if the reactor is formed -- check if the reactor is formed
local function _is_formed() local function _is_formed()
local formed = reactor.isFormed() local formed = reactor.isFormed()
if formed == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(formed) then
-- lost the peripheral or terminated, handled later
_set_fault()
else
self.formed = formed self.formed = formed
end
if not self.state[state_keys.sys_fail] then -- always update, since some ppm failures constitute not being formed
self.state[state_keys.sys_fail] = not formed if not self.state[state_keys.sys_fail] then
end self.state[state_keys.sys_fail] = not self.formed
end end
end end
-- check if the reactor is force disabled -- check if the reactor is force disabled
local function _is_force_disabled() local function _is_force_disabled()
local disabled = reactor.isForceDisabled() local disabled = reactor.isForceDisabled()
if disabled == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(disabled) then
-- lost the peripheral or terminated, handled later
_set_fault()
else
self.force_disabled = disabled self.force_disabled = disabled
if not self.state[state_keys.force_disabled] then if not self.state[state_keys.force_disabled] then
@@ -197,22 +217,16 @@ function plc.rps_init(reactor, is_formed)
-- check for high damage -- check for high damage
local function _high_damage() local function _high_damage()
local damage_percent = reactor.getDamagePercent() local damage_percent = reactor.getDamagePercent()
if damage_percent == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(damage_percent) and not self.state[state_keys.high_dmg] then
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.high_dmg] then
self.state[state_keys.high_dmg] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT self.state[state_keys.high_dmg] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT
end end
end end
-- check if the reactor is at a critically high temperature -- check if the reactor is at a critically high temperature
local function _high_temp() local function _high_temp()
-- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 -- mekanism: MAX_DAMAGE_TEMPERATURE = 1200K
local temp = reactor.getTemperature() local temp = reactor.getTemperature()
if temp == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(temp) and not self.state[state_keys.high_temp] then
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.high_temp] then
self.state[state_keys.high_temp] = temp >= RPS_LIMITS.MAX_DAMAGE_TEMPERATURE self.state[state_keys.high_temp] = temp >= RPS_LIMITS.MAX_DAMAGE_TEMPERATURE
end end
end end
@@ -220,10 +234,7 @@ function plc.rps_init(reactor, is_formed)
-- check if there is very low coolant -- check if there is very low coolant
local function _low_coolant() local function _low_coolant()
local coolant_filled = reactor.getCoolantFilledPercentage() local coolant_filled = reactor.getCoolantFilledPercentage()
if coolant_filled == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(coolant_filled) and not self.state[state_keys.low_coolant] then
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.low_coolant] then
self.state[state_keys.low_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL self.state[state_keys.low_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL
end end
end end
@@ -231,10 +242,7 @@ function plc.rps_init(reactor, is_formed)
-- check for excess waste (>80% filled) -- check for excess waste (>80% filled)
local function _excess_waste() local function _excess_waste()
local w_filled = reactor.getWasteFilledPercentage() local w_filled = reactor.getWasteFilledPercentage()
if w_filled == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(w_filled) and not self.state[state_keys.ex_waste] then
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.ex_waste] then
self.state[state_keys.ex_waste] = w_filled > RPS_LIMITS.MAX_WASTE_FILL self.state[state_keys.ex_waste] = w_filled > RPS_LIMITS.MAX_WASTE_FILL
end end
end end
@@ -242,10 +250,7 @@ function plc.rps_init(reactor, is_formed)
-- check for heated coolant backup (>95% filled) -- check for heated coolant backup (>95% filled)
local function _excess_heated_coolant() local function _excess_heated_coolant()
local hc_filled = reactor.getHeatedCoolantFilledPercentage() local hc_filled = reactor.getHeatedCoolantFilledPercentage()
if hc_filled == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(hc_filled) and not self.state[state_keys.ex_hcoolant] then
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.ex_hcoolant] then
self.state[state_keys.ex_hcoolant] = hc_filled > RPS_LIMITS.MAX_HEATED_COLLANT_FILL self.state[state_keys.ex_hcoolant] = hc_filled > RPS_LIMITS.MAX_HEATED_COLLANT_FILL
end end
end end
@@ -253,10 +258,7 @@ function plc.rps_init(reactor, is_formed)
-- check if there is no fuel -- check if there is no fuel
local function _insufficient_fuel() local function _insufficient_fuel()
local fuel = reactor.getFuelFilledPercentage() local fuel = reactor.getFuelFilledPercentage()
if fuel == ppm.ACCESS_FAULT then if _check_and_handle_ppm_call(fuel) and not self.state[state_keys.no_fuel] then
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.no_fuel] then
self.state[state_keys.no_fuel] = fuel <= RPS_LIMITS.NO_FUEL_FILL self.state[state_keys.no_fuel] = fuel <= RPS_LIMITS.NO_FUEL_FILL
end end
end end
@@ -305,7 +307,7 @@ function plc.rps_init(reactor, is_formed)
log.info("RPS: reactor SCRAM") log.info("RPS: reactor SCRAM")
reactor.scram() reactor.scram()
if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_SCRAM_MSG) then if reactor.__p_is_faulted() and not string.find(reactor.__p_last_fault(), PCALL_SCRAM_MSG) then
log.error("RPS: failed reactor SCRAM") log.error("RPS: failed reactor SCRAM")
return false return false
else else
@@ -323,7 +325,7 @@ function plc.rps_init(reactor, is_formed)
log.info("RPS: reactor start") log.info("RPS: reactor start")
reactor.activate() reactor.activate()
if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_START_MSG) then if reactor.__p_is_faulted() and not string.find(reactor.__p_last_fault(), PCALL_START_MSG) then
log.error("RPS: failed reactor start") log.error("RPS: failed reactor start")
else else
self.reactor_enabled = true self.reactor_enabled = true
@@ -477,13 +479,22 @@ function plc.rps_init(reactor, is_formed)
self.tripped = false self.tripped = false
self.trip_cause = RPS_TRIP_CAUSE.OK self.trip_cause = RPS_TRIP_CAUSE.OK
for i = 1, #self.state do for i = 1, #self.state do self.state[i] = false end
self.state[i] = false
end
if not quiet then log.info("RPS: reset") end if not quiet then log.info("RPS: reset") end
end end
-- partial RPS reset that only clears fault and sys_fail
function public.reset_formed()
self.tripped = false
self.trip_cause = RPS_TRIP_CAUSE.OK
self.state[state_keys.fault] = false
self.state[state_keys.sys_fail] = false
log.info("RPS: partial reset on formed")
end
-- reset the automatic and timeout trip flags, then clear trip if that was the trip cause -- reset the automatic and timeout trip flags, then clear trip if that was the trip cause
function public.auto_reset() function public.auto_reset()
self.state[state_keys.automatic] = false self.state[state_keys.automatic] = false

View File

@@ -18,11 +18,16 @@ local ui = {
} }
-- try to start the UI -- try to start the UI
---@param theme FP_THEME front panel theme
---@param color_mode COLOR_MODE color mode
---@return boolean success, any error_msg ---@return boolean success, any error_msg
function renderer.try_start_ui() function renderer.try_start_ui(theme, color_mode)
local status, msg = true, nil local status, msg = true, nil
if ui.display == nil then if ui.display == nil then
-- set theme
style.set_theme(theme, color_mode)
-- reset terminal -- reset terminal
term.setTextColor(colors.white) term.setTextColor(colors.white)
term.setBackgroundColor(colors.black) term.setBackgroundColor(colors.black)
@@ -30,13 +35,19 @@ function renderer.try_start_ui()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
-- set overridden colors -- set overridden colors
for i = 1, #style.colors do for i = 1, #style.theme.colors do
term.setPaletteColor(style.colors[i].c, style.colors[i].hex) term.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
end
-- apply color mode
local c_mode_overrides = style.theme.color_modes[color_mode]
for i = 1, #c_mode_overrides do
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
end end
-- init front panel view -- init front panel view
status, msg = pcall(function () status, msg = pcall(function ()
ui.display = DisplayBox{window=term.current(),fg_bg=style.root} ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
panel_view(ui.display) panel_view(ui.display)
end) end)
@@ -64,9 +75,9 @@ function renderer.close_ui()
ui.display = nil ui.display = nil
-- restore colors -- restore colors
for i = 1, #style.colors do for i = 1, #style.theme.colors do
local r, g, b = term.nativePaletteColor(style.colors[i].c) local r, g, b = term.nativePaletteColor(style.theme.colors[i].c)
term.setPaletteColor(style.colors[i].c, r, g, b) term.setPaletteColor(style.theme.colors[i].c, r, g, b)
end end
-- reset terminal -- reset terminal

View File

@@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
local renderer = require("reactor-plc.renderer") local renderer = require("reactor-plc.renderer")
local threads = require("reactor-plc.threads") local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "v1.6.11" local R_PLC_VERSION = "v1.7.11"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@@ -31,9 +31,13 @@ if not plc.load_config() then
-- try to reconfigure (user action) -- try to reconfigure (user action)
local success, error = configure.configure(true) local success, error = configure.configure(true)
if success then if success then
assert(plc.load_config(), "failed to load valid configuration") if not plc.load_config() then
println("failed to load a valid configuration, please reconfigure")
return
end
else else
assert(success, "reactor PLC configuration error: " .. error) println("configuration error: " .. error)
return
end end
end end
@@ -51,6 +55,7 @@ log.info("========================================")
println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<")
crash.set_env("reactor-plc", R_PLC_VERSION) crash.set_env("reactor-plc", R_PLC_VERSION)
crash.dbg_log_env()
---------------------------------------- ----------------------------------------
-- main application -- main application
@@ -131,13 +136,13 @@ local function main()
-- we need a reactor, can at least do some things even if it isn't formed though -- we need a reactor, can at least do some things even if it isn't formed though
if plc_state.no_reactor then if plc_state.no_reactor then
println("init> fission reactor not found"); println("init> fission reactor not found")
log.warning("init> no reactor on startup") log.warning("init> no reactor on startup")
plc_state.init_ok = false plc_state.init_ok = false
plc_state.degraded = true plc_state.degraded = true
elseif not smem_dev.reactor.isFormed() then elseif not smem_dev.reactor.isFormed() then
println("init> fission reactor not formed"); println("init> fission reactor is not formed")
log.warning("init> reactor logic adapter present, but reactor is not formed") log.warning("init> reactor logic adapter present, but reactor is not formed")
plc_state.degraded = true plc_state.degraded = true
@@ -172,8 +177,9 @@ local function main()
-- front panel time! -- front panel time!
if not renderer.ui_ready() then if not renderer.ui_ready() then
local message local message
plc_state.fp_ok, message = renderer.try_start_ui() plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
-- ...or not
if not plc_state.fp_ok then if not plc_state.fp_ok then
println_ts(util.c("UI error: ", message)) println_ts(util.c("UI error: ", message))
println("init> running without front panel") println("init> running without front panel")

View File

@@ -71,77 +71,49 @@ function threads.thread__main(smem, init)
-- blink heartbeat indicator -- blink heartbeat indicator
databus.heartbeat() databus.heartbeat()
-- core clock tick -- start next clock timer
if networked then loop_clock.start()
-- start next clock timer
loop_clock.start()
-- send updated data -- send updated data
if nic.is_connected() then if networked and nic.is_connected() then
if plc_comms.is_linked() then if plc_comms.is_linked() then
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
else
if ticks_to_update == 0 then
plc_comms.send_link_req()
ticks_to_update = LINK_TICKS
else else
if ticks_to_update == 0 then ticks_to_update = ticks_to_update - 1
plc_comms.send_link_req()
ticks_to_update = LINK_TICKS
else
ticks_to_update = ticks_to_update - 1
end
end end
end end
end end
-- are we now formed after waiting to be formed? -- check for formed state change
if (not plc_state.reactor_formed) and rps.is_formed() then if (not plc_state.reactor_formed) and rps.is_formed() then
-- push a connect event and unmount it from the PPM -- reactor now formed
local iface = ppm.get_iface(plc_dev.reactor) plc_state.reactor_formed = true
if iface then
log.info("unmounting and remounting unformed reactor")
ppm.unmount(plc_dev.reactor)
local type, device = ppm.mount(iface) println_ts("reactor is now formed.")
log.info("reactor is now formed")
if type == "fissionReactorLogicAdapter" and device ~= nil then -- SCRAM newly formed reactor
-- reconnect reactor smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
plc_dev.reactor = device
-- we need to assume formed here as we cannot check in this main loop -- determine if we are still in a degraded state
-- RPS will identify if it isn't and this will get set false later if (not networked) or nic.is_connected() then
plc_state.reactor_formed = true plc_state.degraded = false
println_ts("reactor reconnected.")
log.info("reactor reconnected")
-- SCRAM newly connected reactor
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
-- determine if we are still in a degraded state
if (not networked) or nic.is_connected() then
plc_state.degraded = false
end
rps.reconnect_reactor(plc_dev.reactor)
if networked then
plc_comms.reconnect_reactor(plc_dev.reactor)
end
-- reset RPS for newly connected reactor
-- without this, is_formed will be out of date and cause it to think its no longer formed again
rps.reset()
else
-- fully lost the reactor now :(
println_ts("reactor lost (failed reconnect)!")
log.error("reactor lost (failed reconnect)")
plc_state.no_reactor = true
plc_state.degraded = true
end
else
log.error("failed to get interface of previously connected reactor", true)
end end
elseif not rps.is_formed() then
-- partial reset of RPS, specific to becoming formed
-- without this, auto control can't resume on chunk load
rps.reset_formed()
elseif plc_state.reactor_formed and not rps.is_formed() then
-- reactor no longer formed -- reactor no longer formed
println_ts("reactor is no longer formed.")
log.info("reactor is no longer formed")
plc_state.reactor_formed = false plc_state.reactor_formed = false
plc_state.degraded = true
end end
-- update indicators -- update indicators
@@ -231,9 +203,9 @@ function threads.thread__main(smem, init)
plc_comms.reconnect_reactor(plc_dev.reactor) plc_comms.reconnect_reactor(plc_dev.reactor)
end end
-- reset RPS for newly connected reactor -- partial reset of RPS, specific to becoming formed/reconnected
-- without this, is_formed will be out of date and cause it to think its no longer formed again -- without this, auto control can't resume on chunk load
rps.reset() rps.reset_formed()
end end
elseif networked and type == "modem" then elseif networked and type == "modem" then
-- note, check init_ok first since nic will be nil if it is false -- note, check init_ok first since nic will be nil if it is false
@@ -370,9 +342,9 @@ function threads.thread__rps(smem)
end end
end end
-- if we are in standalone mode, continuously reset RPS -- if we are in standalone mode and the front panel isn't working, continuously reset RPS
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable -- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
if not networked then rps.reset(true) end if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
-- check safety (SCRAM occurs if tripped) -- check safety (SCRAM occurs if tripped)
if not plc_state.no_reactor then if not plc_state.no_reactor then
@@ -664,8 +636,9 @@ function threads.thread__setpoint_control(smem)
if (type(cur_burn_rate) == "number") and (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then if (type(cur_burn_rate) == "number") and (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then
last_burn_sp = setpoints.burn_rate last_burn_sp = setpoints.burn_rate
-- update without ramp if <= 2.5 mB/t change -- update without ramp if <= 2.5 mB/t increase
running = math.abs(setpoints.burn_rate - cur_burn_rate) > 2.5 -- no need to ramp down, as the ramp up poses the safety risks
running = (setpoints.burn_rate - cur_burn_rate) > 2.5
if running then if running then
log.debug(util.c("SPCTL: starting burn rate ramp from ", cur_burn_rate, " mB/t to ", setpoints.burn_rate, " mB/t")) log.debug(util.c("SPCTL: starting burn rate ramp from ", cur_burn_rate, " mB/t to ", setpoints.burn_rate, " mB/t"))

View File

@@ -2,6 +2,7 @@
-- Configuration GUI -- Configuration GUI
-- --
local constants = require("scada-common.constants")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local rsio = require("scada-common.rsio") local rsio = require("scada-common.rsio")
@@ -9,6 +10,7 @@ local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
local themes = require("graphics.themes")
local DisplayBox = require("graphics.elements.displaybox") local DisplayBox = require("graphics.elements.displaybox")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
@@ -24,56 +26,65 @@ local RadioButton = require("graphics.elements.controls.radio_button")
local NumberField = require("graphics.elements.form.number_field") local NumberField = require("graphics.elements.form.number_field")
local TextField = require("graphics.elements.form.text_field") local TextField = require("graphics.elements.form.text_field")
local IndLight = require("graphics.elements.indicators.light")
local println = util.println local println = util.println
local tri = util.trinary local tri = util.trinary
local cpair = core.cpair local cpair = core.cpair
local IO = rsio.IO local IO = rsio.IO
local IO_LVL = rsio.IO_LVL
local IO_MODE = rsio.IO_MODE
local LEFT = core.ALIGN.LEFT local LEFT = core.ALIGN.LEFT
local CENTER = core.ALIGN.CENTER local CENTER = core.ALIGN.CENTER
local RIGHT = core.ALIGN.RIGHT local RIGHT = core.ALIGN.RIGHT
-- rsio port descriptions -- rsio port descriptions
local PORT_DESC = { local PORT_DESC_MAP = {
"Facility SCRAM", { IO.F_SCRAM, "Facility SCRAM" },
"Facility Acknowledge", { IO.F_ACK, "Facility Acknowledge" },
"Reactor SCRAM", { IO.R_SCRAM, "Reactor SCRAM" },
"Reactor RPS Reset", { IO.R_RESET, "Reactor RPS Reset" },
"Reactor Enable", { IO.R_ENABLE, "Reactor Enable" },
"Unit Acknowledge", { IO.U_ACK, "Unit Acknowledge" },
"Facility Alarm (high prio)", { IO.F_ALARM, "Facility Alarm (high prio)" },
"Facility Alarm (any)", { IO.F_ALARM_ANY, "Facility Alarm (any)" },
"Waste Plutonium Valve", { IO.F_MATRIX_LOW, "Induction Matrix < " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) .. "%" },
"Waste Polonium Valve", { IO.F_MATRIX_HIGH, "Induction Matrix > " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) .. "%" },
"Waste Po Pellets Valve", { IO.F_MATRIX_CHG, "Induction Matrix Charge %" },
"Waste Antimatter Valve", { IO.WASTE_PU, "Waste Plutonium Valve" },
"Reactor Active", { IO.WASTE_PO, "Waste Polonium Valve" },
"Reactor in Auto Control", { IO.WASTE_POPL, "Waste Po Pellets Valve" },
"RPS Tripped", { IO.WASTE_AM, "Waste Antimatter Valve" },
"RPS Auto SCRAM", { IO.R_ACTIVE, "Reactor Active" },
"RPS High Damage", { IO.R_AUTO_CTRL, "Reactor in Auto Control" },
"RPS High Temperature", { IO.R_SCRAMMED, "RPS Tripped" },
"RPS Low Coolant", { IO.R_AUTO_SCRAM, "RPS Auto SCRAM" },
"RPS Excess Heated Coolant", { IO.R_HIGH_DMG, "RPS High Damage" },
"RPS Excess Waste", { IO.R_HIGH_TEMP, "RPS High Temperature" },
"RPS Insufficient Fuel", { IO.R_LOW_COOLANT, "RPS Low Coolant" },
"RPS PLC Fault", { IO.R_EXCESS_HC, "RPS Excess Heated Coolant" },
"RPS Supervisor Timeout", { IO.R_EXCESS_WS, "RPS Excess Waste" },
"Unit Alarm", { IO.R_INSUFF_FUEL, "RPS Insufficient Fuel" },
"Unit Emergency Cool. Valve" { IO.R_PLC_FAULT, "RPS PLC Fault" },
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
{ IO.U_ALARM, "Unit Alarm" },
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" }
} }
-- designation (0 = facility, 1 = unit) -- designation (0 = facility, 1 = unit)
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }
assert(#PORT_DESC == rsio.NUM_PORTS) assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
assert(#PORT_DSGN == rsio.NUM_PORTS) assert(#PORT_DSGN == rsio.NUM_PORTS)
-- changes to the config data/format to let the user know -- changes to the config data/format to let the user know
local changes = { local changes = {
{"v1.7.9", { "ConnTimeout can now have a fractional part" } } { "v1.7.9", { "ConnTimeout can now have a fractional part" } },
{ "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } },
{ "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
} }
---@class rtu_rs_definition ---@class rtu_rs_definition
@@ -98,34 +109,22 @@ local style = {}
style.root = cpair(colors.black, colors.lightGray) style.root = cpair(colors.black, colors.lightGray)
style.header = cpair(colors.white, colors.gray) style.header = cpair(colors.white, colors.gray)
style.colors = { style.colors = themes.smooth_stone.colors
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.lime, hex = 0x80ff80 },
{ c = colors.green, hex = 0x4aee8a },
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee },
{ c = colors.pink, hex = 0xf26ba2 },
{ c = colors.magenta, hex = 0xf9488a },
{ c = colors.lightGray, hex = 0xcacaca },
{ c = colors.gray, hex = 0x575757 }
}
local bw_fg_bg = cpair(colors.black, colors.white) local bw_fg_bg = cpair(colors.black, colors.white)
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray) local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
local nav_fg_bg = bw_fg_bg local nav_fg_bg = bw_fg_bg
local btn_act_fg_bg = cpair(colors.white, colors.gray) local btn_act_fg_bg = cpair(colors.white, colors.gray)
---@class _rtu_cfg_tool_ctl
local tool_ctl = { local tool_ctl = {
ask_config = false, ask_config = false,
has_config = false, has_config = false,
viewing_config = false, viewing_config = false,
importing_legacy = false, importing_legacy = false,
importing_any_dc = false, importing_any_dc = false,
peri_cfg_editing = false, ---@type string|false jumped_to_color = false,
peri_cfg_editing = false, ---@type integer|false
peri_cfg_manual = false, peri_cfg_manual = false,
rs_cfg_port = IO.F_SCRAM, ---@type IO_PORT rs_cfg_port = IO.F_SCRAM, ---@type IO_PORT
rs_cfg_editing = false, ---@type integer|false rs_cfg_editing = false, ---@type integer|false
@@ -133,6 +132,9 @@ local tool_ctl = {
view_gw_cfg = nil, ---@type graphics_element view_gw_cfg = nil, ---@type graphics_element
dev_cfg = nil, ---@type graphics_element dev_cfg = nil, ---@type graphics_element
rs_cfg = nil, ---@type graphics_element rs_cfg = nil, ---@type graphics_element
color_cfg = nil, ---@type graphics_element
color_next = nil, ---@type graphics_element
color_apply = nil, ---@type graphics_element
settings_apply = nil, ---@type graphics_element settings_apply = nil, ---@type graphics_element
settings_confirm = nil, ---@type graphics_element settings_confirm = nil, ---@type graphics_element
@@ -181,7 +183,9 @@ local tmp_cfg = {
AuthKey = nil, ---@type string|nil AuthKey = nil, ---@type string|nil
LogMode = 0, LogMode = 0,
LogPath = "", LogPath = "",
LogDebug = false LogDebug = false,
FrontPanelTheme = 1,
ColorMode = 1
} }
---@class rtu_config ---@class rtu_config
@@ -198,7 +202,9 @@ local fields = {
{ "AuthKey", "Facility Auth Key", "" }, { "AuthKey", "Facility Auth Key", "" },
{ "LogMode", "Log Mode", log.MODE.APPEND }, { "LogMode", "Log Mode", log.MODE.APPEND },
{ "LogPath", "Log Path", "/log.txt" }, { "LogPath", "Log Path", "/log.txt" },
{ "LogDebug","Log Debug Messages", false } { "LogDebug", "Log Debug Messages", false },
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
} }
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" } local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
@@ -266,12 +272,13 @@ local function config_view(display)
local spkr_cfg = Div{parent=root_pane_div,x=1,y=1} local spkr_cfg = Div{parent=root_pane_div,x=1,y=1}
local net_cfg = Div{parent=root_pane_div,x=1,y=1} local net_cfg = Div{parent=root_pane_div,x=1,y=1}
local log_cfg = Div{parent=root_pane_div,x=1,y=1} local log_cfg = Div{parent=root_pane_div,x=1,y=1}
local clr_cfg = Div{parent=root_pane_div,x=1,y=1}
local summary = Div{parent=root_pane_div,x=1,y=1} local summary = Div{parent=root_pane_div,x=1,y=1}
local changelog = Div{parent=root_pane_div,x=1,y=1} local changelog = Div{parent=root_pane_div,x=1,y=1}
local peri_cfg = Div{parent=root_pane_div,x=1,y=1} local peri_cfg = Div{parent=root_pane_div,x=1,y=1}
local rs_cfg = Div{parent=root_pane_div,x=1,y=1} local rs_cfg = Div{parent=root_pane_div,x=1,y=1}
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,summary,changelog,peri_cfg,rs_cfg}} local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg}}
--#region Main Page --#region Main Page
@@ -290,7 +297,7 @@ local function config_view(display)
tool_ctl.gen_summary(settings_cfg) tool_ctl.gen_summary(settings_cfg)
tool_ctl.settings_apply.hide(true) tool_ctl.settings_apply.hide(true)
tool_ctl.settings_confirm.hide(true) tool_ctl.settings_confirm.hide(true)
main_pane.set_value(5) main_pane.set_value(6)
end end
if fs.exists("/rtu/config.lua") then if fs.exists("/rtu/config.lua") then
@@ -300,12 +307,12 @@ local function config_view(display)
local function show_peri_conns() local function show_peri_conns()
tool_ctl.gen_peri_summary(ini_cfg) tool_ctl.gen_peri_summary(ini_cfg)
main_pane.set_value(7) main_pane.set_value(8)
end end
local function show_rs_conns() local function show_rs_conns()
tool_ctl.gen_rs_summary(ini_cfg) tool_ctl.gen_rs_summary(ini_cfg)
main_pane.set_value(8) main_pane.set_value(9)
end end
PushButton{parent=main_page,x=2,y=y_start,min_width=19,text="Configure Gateway",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} PushButton{parent=main_page,x=2,y=y_start,min_width=19,text="Configure Gateway",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
@@ -313,15 +320,24 @@ local function config_view(display)
tool_ctl.dev_cfg = PushButton{parent=main_page,x=2,y=y_start+4,min_width=24,text="Peripheral Connections",callback=show_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.dev_cfg = PushButton{parent=main_page,x=2,y=y_start+4,min_width=24,text="Peripheral Connections",callback=show_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
tool_ctl.rs_cfg = PushButton{parent=main_page,x=2,y=y_start+6,min_width=22,text="Redstone Connections",callback=show_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.rs_cfg = PushButton{parent=main_page,x=2,y=y_start+6,min_width=22,text="Redstone Connections",callback=show_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
local function jump_color()
tool_ctl.jumped_to_color = true
tool_ctl.color_next.hide(true)
tool_ctl.color_apply.show()
main_pane.set_value(5)
end
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
if not tool_ctl.has_config then if not tool_ctl.has_config then
tool_ctl.view_gw_cfg.disable() tool_ctl.view_gw_cfg.disable()
tool_ctl.dev_cfg.disable() tool_ctl.dev_cfg.disable()
tool_ctl.rs_cfg.disable() tool_ctl.rs_cfg.disable()
tool_ctl.color_cfg.disable()
end end
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion --#endregion
--#region Speakers --#region Speakers
@@ -432,7 +448,7 @@ local function config_view(display)
TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"} TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"}
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end local function censor_key(enable) censor(tri(enable, "*", nil)) end
local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
@@ -480,11 +496,8 @@ local function config_view(display)
tmp_cfg.LogMode = mode.get_value() - 1 tmp_cfg.LogMode = mode.get_value() - 1
tmp_cfg.LogPath = path.get_value() tmp_cfg.LogPath = path.get_value()
tmp_cfg.LogDebug = en_dbg.get_value() tmp_cfg.LogDebug = en_dbg.get_value()
tool_ctl.gen_summary(tmp_cfg) tool_ctl.color_apply.hide(true)
tool_ctl.viewing_config = false tool_ctl.color_next.show()
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
tool_ctl.settings_confirm.hide(true)
main_pane.set_value(5) main_pane.set_value(5)
else path_err.show() end else path_err.show() end
end end
@@ -494,6 +507,112 @@ local function config_view(display)
--#endregion --#endregion
--#region Color Options
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."}
TextBox{parent=clr_c_2,x=21,y=7,height=1,text="Preview"}
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
local function recolor(value)
local c = themes.smooth_stone.color_modes[value]
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
b_off.hide()
g_off.show()
else
g_off.hide()
b_off.show()
end
if #c == 0 then
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
else
term.setPaletteColor(colors.green, c[1].hex)
term.setPaletteColor(colors.yellow, c[2].hex)
term.setPaletteColor(colors.red, c[3].hex)
end
end
TextBox{parent=clr_c_2,x=1,y=7,height=1,width=10,text="Color Mode"}
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local function back_from_colors()
main_pane.set_value(tri(tool_ctl.jumped_to_color, 1, 4))
tool_ctl.jumped_to_color = false
recolor(1)
end
local function show_access()
clr_pane.set_value(2)
recolor(c_mode.get_value())
end
local function submit_colors()
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
tmp_cfg.ColorMode = c_mode.get_value()
if tool_ctl.jumped_to_color then
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
settings.set("ColorMode", tmp_cfg.ColorMode)
if settings.save("/rtu.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
clr_pane.set_value(3)
else
clr_pane.set_value(4)
end
else
tool_ctl.gen_summary(tmp_cfg)
tool_ctl.viewing_config = false
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
tool_ctl.settings_confirm.hide(true)
main_pane.set_value(6)
end
end
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply.hide(true)
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Summary and Saving --#region Summary and Saving
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49} local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
@@ -520,7 +639,7 @@ local function config_view(display)
end end
tool_ctl.viewing_config = false tool_ctl.viewing_config = false
else main_pane.set_value(4) end else main_pane.set_value(5) end
end end
---@param element graphics_element ---@param element graphics_element
@@ -552,6 +671,8 @@ local function config_view(display)
try_set(mode, ini_cfg.LogMode) try_set(mode, ini_cfg.LogMode)
try_set(path, ini_cfg.LogPath) try_set(path, ini_cfg.LogPath)
try_set(en_dbg, ini_cfg.LogDebug) try_set(en_dbg, ini_cfg.LogDebug)
try_set(fp_theme, ini_cfg.FrontPanelTheme)
try_set(c_mode, ini_cfg.ColorMode)
if not exclude_conns then if not exclude_conns then
tmp_cfg.Peripherals = deep_copy_peri(ini_cfg.Peripherals) tmp_cfg.Peripherals = deep_copy_peri(ini_cfg.Peripherals)
@@ -589,7 +710,20 @@ local function config_view(display)
PushButton{parent=sum_c_3,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_3,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_3,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_3,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
local function jump_peri_conns()
tool_ctl.go_home()
show_peri_conns()
end
local function jump_rs_conns()
tool_ctl.go_home()
show_rs_conns()
end
TextBox{parent=sum_c_4,x=1,y=1,height=1,text="Settings saved!"} TextBox{parent=sum_c_4,x=1,y=1,height=1,text="Settings saved!"}
TextBox{parent=sum_c_4,x=1,y=3,height=4,text="Remember to configure any peripherals or redstone that you have connected to this RTU gateway if you have not already done so, or if you have added, removed, or modified any of them."}
PushButton{parent=sum_c_4,x=1,y=8,min_width=24,text="Peripheral Connections",callback=jump_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_4,x=1,y=10,min_width=22,text="Redstone Connections",callback=jump_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
@@ -769,7 +903,7 @@ local function config_view(display)
tool_ctl.p_desc.reposition(1, 8) tool_ctl.p_desc.reposition(1, 8)
tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.") tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.")
elseif type == "inductionPort" or type == "spsPort" then elseif type == "inductionPort" or type == "spsPort" then
local dev = util.trinary(type == "inductionPort", "induction matrix", "SPS") local dev = tri(type == "inductionPort", "induction matrix", "SPS")
tool_ctl.p_idx.hide(true) tool_ctl.p_idx.hide(true)
tool_ctl.p_unit.hide(true) tool_ctl.p_unit.hide(true)
tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.") tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.")
@@ -795,7 +929,7 @@ local function config_view(display)
tool_ctl.ppm_devs.remove_all() tool_ctl.ppm_devs.remove_all()
for name, entry in pairs(mounts) do for name, entry in pairs(mounts) do
if util.table_contains(RTU_DEV_TYPES, entry.type) then if util.table_contains(RTU_DEV_TYPES, entry.type) then
local bkg = util.trinary(alternate, colors.white, colors.lightGray) local bkg = tri(alternate, colors.white, colors.lightGray)
---@cast entry ppm_entry ---@cast entry ppm_entry
local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)} local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)}
@@ -957,8 +1091,9 @@ local function config_view(display)
local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49}
local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6}} local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7}}
TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)} TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)}
@@ -1015,9 +1150,23 @@ local function config_view(display)
text = "You selected the ALL_WASTE shortcut." text = "You selected the ALL_WASTE shortcut."
else else
tool_ctl.rs_cfg_shortcut.hide(true) tool_ctl.rs_cfg_shortcut.hide(true)
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
tool_ctl.rs_cfg_color.show() tool_ctl.rs_cfg_color.show()
text = "You selected " .. rsio.to_string(port) .. " (for "
local io_type = "analog input "
local io_mode = rsio.get_io_mode(port)
local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "")
if io_mode == IO_MODE.DIGITAL_IN then
io_type = inv .. "digital input "
elseif io_mode == IO_MODE.DIGITAL_OUT then
io_type = inv .. "digital output "
elseif io_mode == IO_MODE.ANALOG_OUT then
io_type = "analog output "
end
text = "You selected the " .. io_type .. rsio.to_string(port) .. " (for "
if PORT_DSGN[port] == 1 then if PORT_DSGN[port] == 1 then
text = text .. "a unit)." text = text .. "a unit)."
tool_ctl.rs_cfg_unit_l.show() tool_ctl.rs_cfg_unit_l.show()
@@ -1039,25 +1188,35 @@ local function config_view(display)
PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)} PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)}
TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)} TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)} TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)}
for i = 1, rsio.NUM_PORTS do for i = 1, rsio.NUM_PORTS do
local name = rsio.to_string(i) local p = PORT_DESC_MAP[i][1]
local io_dir = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, "[in]", "[out]") local name = rsio.to_string(p)
local btn_color = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue) local io_dir = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]")
local btn_color = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
local entry = Div{parent=rs_ports,height=1} local entry = Div{parent=rs_ports,height=1}
PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(i)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)} PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(p)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)}
TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC[i],fg_bg=cpair(colors.gray,colors.white)} TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)}
end end
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""} tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""}
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text="Unit ID"} PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"} TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"}
local side = Radio2D{parent=rs_c_3,x=1,y=4,rows=2,columns=3,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red} TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"}
TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"}
PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,height=1,text="Output Side"}
local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red}
tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,height=1,text="Unit ID"}
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
local function set_bundled(bundled) local function set_bundled(bundled)
if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end
@@ -1088,10 +1247,10 @@ local function config_view(display)
if port >= 0 then if port >= 0 then
---@type rtu_rs_definition ---@type rtu_rs_definition
local def = { local def = {
unit = util.trinary(PORT_DSGN[port] == 1, u, nil), unit = tri(PORT_DSGN[port] == 1, u, nil),
port = port, port = port,
side = side_options_map[side.get_value()], side = side_options_map[side.get_value()],
color = util.trinary(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil) color = tri(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil)
} }
if tool_ctl.rs_cfg_editing == false then if tool_ctl.rs_cfg_editing == false then
@@ -1105,10 +1264,10 @@ local function config_view(display)
local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime } local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime }
for i = 0, 3 do for i = 0, 3 do
table.insert(tmp_cfg.Redstone, { table.insert(tmp_cfg.Redstone, {
unit = util.trinary(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil), unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil),
port = IO.WASTE_PU + i, port = IO.WASTE_PU + i,
side = util.trinary(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]), side = tri(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]),
color = util.trinary(bundled.get_value(), default_colors[i + 1], nil) color = tri(bundled.get_value(), default_colors[i + 1], nil)
}) })
end end
end end
@@ -1161,7 +1320,7 @@ local function config_view(display)
peri_import_list.remove_all() peri_import_list.remove_all()
for _, entry in ipairs(config.RTU_DEVICES) do for _, entry in ipairs(config.RTU_DEVICES) do
local for_facility = entry.for_reactor == 0 local for_facility = entry.for_reactor == 0
local ini_unit = util.trinary(for_facility, nil, entry.for_reactor) local ini_unit = tri(for_facility, nil, entry.for_reactor)
local def = { name = entry.name, unit = ini_unit, index = entry.index } local def = { name = entry.name, unit = ini_unit, index = entry.index }
local mount = mounts[def.name] ---@type ppm_entry|nil local mount = mounts[def.name] ---@type ppm_entry|nil
@@ -1240,7 +1399,7 @@ local function config_view(display)
table.insert(tmp_cfg.Redstone, def) table.insert(tmp_cfg.Redstone, def)
local name = rsio.to_string(def.port) local name = rsio.to_string(def.port)
local io_dir = util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
local conn = def.side local conn = def.side
local unit = "facility" local unit = "facility"
@@ -1257,7 +1416,7 @@ local function config_view(display)
tool_ctl.gen_summary(tmp_cfg) tool_ctl.gen_summary(tmp_cfg)
if tool_ctl.importing_any_dc then sum_pane.set_value(7) else sum_pane.set_value(1) end if tool_ctl.importing_any_dc then sum_pane.set_value(7) else sum_pane.set_value(1) end
main_pane.set_value(5) main_pane.set_value(6)
tool_ctl.settings_apply.hide(true) tool_ctl.settings_apply.hide(true)
tool_ctl.settings_confirm.show() tool_ctl.settings_confirm.show()
tool_ctl.importing_legacy = true tool_ctl.importing_legacy = true
@@ -1271,6 +1430,7 @@ local function config_view(display)
main_pane.set_value(1) main_pane.set_value(1)
net_pane.set_value(1) net_pane.set_value(1)
clr_pane.set_value(1)
sum_pane.set_value(1) sum_pane.set_value(1)
peri_pane.set_value(1) peri_pane.set_value(1)
rs_pane.set_value(1) rs_pane.set_value(1)
@@ -1301,11 +1461,17 @@ local function config_view(display)
local raw = cfg[f[1]] local raw = cfg[f[1]]
local val = util.strval(raw) local val = util.strval(raw)
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace")
elseif f[1] == "FrontPanelTheme" then
val = util.strval(themes.fp_theme_name(raw))
elseif f[1] == "ColorMode" then
val = util.strval(themes.color_mode_name(raw))
end
if val == "nil" then val = "<not set>" end if val == "nil" then val = "<not set>" end
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
alternate = not alternate alternate = not alternate
if string.len(val) > val_max_w then if string.len(val) > val_max_w then
@@ -1419,7 +1585,7 @@ local function config_view(display)
end end
tool_ctl.rs_cfg_selection.set_value(text) tool_ctl.rs_cfg_selection.set_value(text)
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
side.set_value(side_to_idx(def.side)) side.set_value(side_to_idx(def.side))
bundled.set_value(def.color ~= nil) bundled.set_value(def.color ~= nil)
tool_ctl.rs_cfg_color.set_value(value) tool_ctl.rs_cfg_color.set_value(value)
@@ -1440,7 +1606,7 @@ local function config_view(display)
local def = cfg.Redstone[i] ---@type rtu_rs_definition local def = cfg.Redstone[i] ---@type rtu_rs_definition
local name = rsio.to_string(def.port) local name = rsio.to_string(def.port)
local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
local conn = def.side local conn = def.side
local unit = util.strval(def.unit or "F") local unit = util.strval(def.unit or "F")
@@ -1503,9 +1669,11 @@ function configurator.configure(ask_config)
elseif event == "paste" then elseif event == "paste" then
display.handle_paste(param1) display.handle_paste(param1)
elseif event == "peripheral_detach" then elseif event == "peripheral_detach" then
---@diagnostic disable-next-line: discard-returns
ppm.handle_unmount(param1) ppm.handle_unmount(param1)
tool_ctl.update_peri_list() tool_ctl.update_peri_list()
elseif event == "peripheral" then elseif event == "peripheral" then
---@diagnostic disable-next-line: discard-returns
ppm.mount(param1) ppm.mount(param1)
tool_ctl.update_peri_list() tool_ctl.update_peri_list()
end end

View File

@@ -7,60 +7,51 @@ local boilerv_rtu = {}
---@param boiler table ---@param boiler table
---@return rtu_device interface, boolean faulted ---@return rtu_device interface, boolean faulted
function boilerv_rtu.new(boiler) function boilerv_rtu.new(boiler)
local unit = rtu.init_unit() local unit = rtu.init_unit(boiler)
-- disable auto fault clearing
boiler.__p_clear_fault()
boiler.__p_disable_afc()
-- discrete inputs -- -- discrete inputs --
unit.connect_di(boiler.isFormed) unit.connect_di("isFormed")
-- coils -- -- coils --
-- none -- none
-- input registers -- -- input registers --
-- multiblock properties -- multiblock properties
unit.connect_input_reg(boiler.getLength) unit.connect_input_reg("getLength")
unit.connect_input_reg(boiler.getWidth) unit.connect_input_reg("getWidth")
unit.connect_input_reg(boiler.getHeight) unit.connect_input_reg("getHeight")
unit.connect_input_reg(boiler.getMinPos) unit.connect_input_reg("getMinPos")
unit.connect_input_reg(boiler.getMaxPos) unit.connect_input_reg("getMaxPos")
-- build properties -- build properties
unit.connect_input_reg(boiler.getBoilCapacity) unit.connect_input_reg("getBoilCapacity")
unit.connect_input_reg(boiler.getSteamCapacity) unit.connect_input_reg("getSteamCapacity")
unit.connect_input_reg(boiler.getWaterCapacity) unit.connect_input_reg("getWaterCapacity")
unit.connect_input_reg(boiler.getHeatedCoolantCapacity) unit.connect_input_reg("getHeatedCoolantCapacity")
unit.connect_input_reg(boiler.getCooledCoolantCapacity) unit.connect_input_reg("getCooledCoolantCapacity")
unit.connect_input_reg(boiler.getSuperheaters) unit.connect_input_reg("getSuperheaters")
unit.connect_input_reg(boiler.getMaxBoilRate) unit.connect_input_reg("getMaxBoilRate")
-- current state -- current state
unit.connect_input_reg(boiler.getTemperature) unit.connect_input_reg("getTemperature")
unit.connect_input_reg(boiler.getBoilRate) unit.connect_input_reg("getBoilRate")
unit.connect_input_reg(boiler.getEnvironmentalLoss) unit.connect_input_reg("getEnvironmentalLoss")
-- tanks -- tanks
unit.connect_input_reg(boiler.getSteam) unit.connect_input_reg("getSteam")
unit.connect_input_reg(boiler.getSteamNeeded) unit.connect_input_reg("getSteamNeeded")
unit.connect_input_reg(boiler.getSteamFilledPercentage) unit.connect_input_reg("getSteamFilledPercentage")
unit.connect_input_reg(boiler.getWater) unit.connect_input_reg("getWater")
unit.connect_input_reg(boiler.getWaterNeeded) unit.connect_input_reg("getWaterNeeded")
unit.connect_input_reg(boiler.getWaterFilledPercentage) unit.connect_input_reg("getWaterFilledPercentage")
unit.connect_input_reg(boiler.getHeatedCoolant) unit.connect_input_reg("getHeatedCoolant")
unit.connect_input_reg(boiler.getHeatedCoolantNeeded) unit.connect_input_reg("getHeatedCoolantNeeded")
unit.connect_input_reg(boiler.getHeatedCoolantFilledPercentage) unit.connect_input_reg("getHeatedCoolantFilledPercentage")
unit.connect_input_reg(boiler.getCooledCoolant) unit.connect_input_reg("getCooledCoolant")
unit.connect_input_reg(boiler.getCooledCoolantNeeded) unit.connect_input_reg("getCooledCoolantNeeded")
unit.connect_input_reg(boiler.getCooledCoolantFilledPercentage) unit.connect_input_reg("getCooledCoolantFilledPercentage")
-- holding registers -- -- holding registers --
-- none -- none
-- check if any calls faulted return unit.interface(), false
local faulted = boiler.__p_is_faulted()
boiler.__p_clear_fault()
boiler.__p_enable_afc()
return unit.interface(), faulted
end end
return boilerv_rtu return boilerv_rtu

View File

@@ -7,14 +7,10 @@ local dynamicv_rtu = {}
---@param dynamic_tank table ---@param dynamic_tank table
---@return rtu_device interface, boolean faulted ---@return rtu_device interface, boolean faulted
function dynamicv_rtu.new(dynamic_tank) function dynamicv_rtu.new(dynamic_tank)
local unit = rtu.init_unit() local unit = rtu.init_unit(dynamic_tank)
-- disable auto fault clearing
dynamic_tank.__p_clear_fault()
dynamic_tank.__p_disable_afc()
-- discrete inputs -- -- discrete inputs --
unit.connect_di(dynamic_tank.isFormed) unit.connect_di("isFormed")
-- coils -- -- coils --
unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end) unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end)
@@ -22,27 +18,22 @@ function dynamicv_rtu.new(dynamic_tank)
-- input registers -- -- input registers --
-- multiblock properties -- multiblock properties
unit.connect_input_reg(dynamic_tank.getLength) unit.connect_input_reg("getLength")
unit.connect_input_reg(dynamic_tank.getWidth) unit.connect_input_reg("getWidth")
unit.connect_input_reg(dynamic_tank.getHeight) unit.connect_input_reg("getHeight")
unit.connect_input_reg(dynamic_tank.getMinPos) unit.connect_input_reg("getMinPos")
unit.connect_input_reg(dynamic_tank.getMaxPos) unit.connect_input_reg("getMaxPos")
-- build properties -- build properties
unit.connect_input_reg(dynamic_tank.getTankCapacity) unit.connect_input_reg("getTankCapacity")
unit.connect_input_reg(dynamic_tank.getChemicalTankCapacity) unit.connect_input_reg("getChemicalTankCapacity")
-- tanks/containers -- tanks/containers
unit.connect_input_reg(dynamic_tank.getStored) unit.connect_input_reg("getStored")
unit.connect_input_reg(dynamic_tank.getFilledPercentage) unit.connect_input_reg("getFilledPercentage")
-- holding registers -- -- holding registers --
unit.connect_holding_reg(dynamic_tank.getContainerEditMode, dynamic_tank.setContainerEditMode) unit.connect_holding_reg("getContainerEditMode", "setContainerEditMode")
-- check if any calls faulted return unit.interface(), false
local faulted = dynamic_tank.__p_is_faulted()
dynamic_tank.__p_clear_fault()
dynamic_tank.__p_enable_afc()
return unit.interface(), faulted
end end
return dynamicv_rtu return dynamicv_rtu

View File

@@ -7,7 +7,7 @@ local envd_rtu = {}
---@param envd table ---@param envd table
---@return rtu_device interface, boolean faulted ---@return rtu_device interface, boolean faulted
function envd_rtu.new(envd) function envd_rtu.new(envd)
local unit = rtu.init_unit() local unit = rtu.init_unit(envd)
-- disable auto fault clearing -- disable auto fault clearing
envd.__p_clear_fault() envd.__p_clear_fault()

View File

@@ -7,47 +7,38 @@ local imatrix_rtu = {}
---@param imatrix table ---@param imatrix table
---@return rtu_device interface, boolean faulted ---@return rtu_device interface, boolean faulted
function imatrix_rtu.new(imatrix) function imatrix_rtu.new(imatrix)
local unit = rtu.init_unit() local unit = rtu.init_unit(imatrix)
-- disable auto fault clearing
imatrix.__p_clear_fault()
imatrix.__p_disable_afc()
-- discrete inputs -- -- discrete inputs --
unit.connect_di(imatrix.isFormed) unit.connect_di("isFormed")
-- coils -- -- coils --
-- none -- none
-- input registers -- -- input registers --
-- multiblock properties -- multiblock properties
unit.connect_input_reg(imatrix.getLength) unit.connect_input_reg("getLength")
unit.connect_input_reg(imatrix.getWidth) unit.connect_input_reg("getWidth")
unit.connect_input_reg(imatrix.getHeight) unit.connect_input_reg("getHeight")
unit.connect_input_reg(imatrix.getMinPos) unit.connect_input_reg("getMinPos")
unit.connect_input_reg(imatrix.getMaxPos) unit.connect_input_reg("getMaxPos")
-- build properties -- build properties
unit.connect_input_reg(imatrix.getMaxEnergy) unit.connect_input_reg("getMaxEnergy")
unit.connect_input_reg(imatrix.getTransferCap) unit.connect_input_reg("getTransferCap")
unit.connect_input_reg(imatrix.getInstalledCells) unit.connect_input_reg("getInstalledCells")
unit.connect_input_reg(imatrix.getInstalledProviders) unit.connect_input_reg("getInstalledProviders")
-- I/O rates -- I/O rates
unit.connect_input_reg(imatrix.getLastInput) unit.connect_input_reg("getLastInput")
unit.connect_input_reg(imatrix.getLastOutput) unit.connect_input_reg("getLastOutput")
-- tanks -- tanks
unit.connect_input_reg(imatrix.getEnergy) unit.connect_input_reg("getEnergy")
unit.connect_input_reg(imatrix.getEnergyNeeded) unit.connect_input_reg("getEnergyNeeded")
unit.connect_input_reg(imatrix.getEnergyFilledPercentage) unit.connect_input_reg("getEnergyFilledPercentage")
-- holding registers -- -- holding registers --
-- none -- none
-- check if any calls faulted return unit.interface(), false
local faulted = imatrix.__p_is_faulted()
imatrix.__p_clear_fault()
imatrix.__p_enable_afc()
return unit.interface(), faulted
end end
return imatrix_rtu return imatrix_rtu

View File

@@ -7,7 +7,7 @@ local sna_rtu = {}
---@param sna table ---@param sna table
---@return rtu_device interface, boolean faulted ---@return rtu_device interface, boolean faulted
function sna_rtu.new(sna) function sna_rtu.new(sna)
local unit = rtu.init_unit() local unit = rtu.init_unit(sna)
-- disable auto fault clearing -- disable auto fault clearing
sna.__p_clear_fault() sna.__p_clear_fault()

View File

@@ -7,52 +7,43 @@ local sps_rtu = {}
---@param sps table ---@param sps table
---@return rtu_device interface, boolean faulted ---@return rtu_device interface, boolean faulted
function sps_rtu.new(sps) function sps_rtu.new(sps)
local unit = rtu.init_unit() local unit = rtu.init_unit(sps)
-- disable auto fault clearing
sps.__p_clear_fault()
sps.__p_disable_afc()
-- discrete inputs -- -- discrete inputs --
unit.connect_di(sps.isFormed) unit.connect_di("isFormed")
-- coils -- -- coils --
-- none -- none
-- input registers -- -- input registers --
-- multiblock properties -- multiblock properties
unit.connect_input_reg(sps.getLength) unit.connect_input_reg("getLength")
unit.connect_input_reg(sps.getWidth) unit.connect_input_reg("getWidth")
unit.connect_input_reg(sps.getHeight) unit.connect_input_reg("getHeight")
unit.connect_input_reg(sps.getMinPos) unit.connect_input_reg("getMinPos")
unit.connect_input_reg(sps.getMaxPos) unit.connect_input_reg("getMaxPos")
-- build properties -- build properties
unit.connect_input_reg(sps.getCoils) unit.connect_input_reg("getCoils")
unit.connect_input_reg(sps.getInputCapacity) unit.connect_input_reg("getInputCapacity")
unit.connect_input_reg(sps.getOutputCapacity) unit.connect_input_reg("getOutputCapacity")
unit.connect_input_reg(sps.getMaxEnergy) unit.connect_input_reg("getMaxEnergy")
-- current state -- current state
unit.connect_input_reg(sps.getProcessRate) unit.connect_input_reg("getProcessRate")
-- tanks -- tanks
unit.connect_input_reg(sps.getInput) unit.connect_input_reg("getInput")
unit.connect_input_reg(sps.getInputNeeded) unit.connect_input_reg("getInputNeeded")
unit.connect_input_reg(sps.getInputFilledPercentage) unit.connect_input_reg("getInputFilledPercentage")
unit.connect_input_reg(sps.getOutput) unit.connect_input_reg("getOutput")
unit.connect_input_reg(sps.getOutputNeeded) unit.connect_input_reg("getOutputNeeded")
unit.connect_input_reg(sps.getOutputFilledPercentage) unit.connect_input_reg("getOutputFilledPercentage")
unit.connect_input_reg(sps.getEnergy) unit.connect_input_reg("getEnergy")
unit.connect_input_reg(sps.getEnergyNeeded) unit.connect_input_reg("getEnergyNeeded")
unit.connect_input_reg(sps.getEnergyFilledPercentage) unit.connect_input_reg("getEnergyFilledPercentage")
-- holding registers -- -- holding registers --
-- none -- none
-- check if any calls faulted return unit.interface(), false
local faulted = sps.__p_is_faulted()
sps.__p_clear_fault()
sps.__p_enable_afc()
return unit.interface(), faulted
end end
return sps_rtu return sps_rtu

View File

@@ -7,14 +7,10 @@ local turbinev_rtu = {}
---@param turbine table ---@param turbine table
---@return rtu_device interface, boolean faulted ---@return rtu_device interface, boolean faulted
function turbinev_rtu.new(turbine) function turbinev_rtu.new(turbine)
local unit = rtu.init_unit() local unit = rtu.init_unit(turbine)
-- disable auto fault clearing
turbine.__p_clear_fault()
turbine.__p_disable_afc()
-- discrete inputs -- -- discrete inputs --
unit.connect_di(turbine.isFormed) unit.connect_di("isFormed")
-- coils -- -- coils --
unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end) unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end)
@@ -22,44 +18,39 @@ function turbinev_rtu.new(turbine)
-- input registers -- -- input registers --
-- multiblock properties -- multiblock properties
unit.connect_input_reg(turbine.getLength) unit.connect_input_reg("getLength")
unit.connect_input_reg(turbine.getWidth) unit.connect_input_reg("getWidth")
unit.connect_input_reg(turbine.getHeight) unit.connect_input_reg("getHeight")
unit.connect_input_reg(turbine.getMinPos) unit.connect_input_reg("getMinPos")
unit.connect_input_reg(turbine.getMaxPos) unit.connect_input_reg("getMaxPos")
-- build properties -- build properties
unit.connect_input_reg(turbine.getBlades) unit.connect_input_reg("getBlades")
unit.connect_input_reg(turbine.getCoils) unit.connect_input_reg("getCoils")
unit.connect_input_reg(turbine.getVents) unit.connect_input_reg("getVents")
unit.connect_input_reg(turbine.getDispersers) unit.connect_input_reg("getDispersers")
unit.connect_input_reg(turbine.getCondensers) unit.connect_input_reg("getCondensers")
unit.connect_input_reg(turbine.getSteamCapacity) unit.connect_input_reg("getSteamCapacity")
unit.connect_input_reg(turbine.getMaxEnergy) unit.connect_input_reg("getMaxEnergy")
unit.connect_input_reg(turbine.getMaxFlowRate) unit.connect_input_reg("getMaxFlowRate")
unit.connect_input_reg(turbine.getMaxProduction) unit.connect_input_reg("getMaxProduction")
unit.connect_input_reg(turbine.getMaxWaterOutput) unit.connect_input_reg("getMaxWaterOutput")
-- current state -- current state
unit.connect_input_reg(turbine.getFlowRate) unit.connect_input_reg("getFlowRate")
unit.connect_input_reg(turbine.getProductionRate) unit.connect_input_reg("getProductionRate")
unit.connect_input_reg(turbine.getLastSteamInputRate) unit.connect_input_reg("getLastSteamInputRate")
unit.connect_input_reg(turbine.getDumpingMode) unit.connect_input_reg("getDumpingMode")
-- tanks/containers -- tanks/containers
unit.connect_input_reg(turbine.getSteam) unit.connect_input_reg("getSteam")
unit.connect_input_reg(turbine.getSteamNeeded) unit.connect_input_reg("getSteamNeeded")
unit.connect_input_reg(turbine.getSteamFilledPercentage) unit.connect_input_reg("getSteamFilledPercentage")
unit.connect_input_reg(turbine.getEnergy) unit.connect_input_reg("getEnergy")
unit.connect_input_reg(turbine.getEnergyNeeded) unit.connect_input_reg("getEnergyNeeded")
unit.connect_input_reg(turbine.getEnergyFilledPercentage) unit.connect_input_reg("getEnergyFilledPercentage")
-- holding registers -- -- holding registers --
unit.connect_holding_reg(turbine.getDumpingMode, turbine.setDumpingMode) unit.connect_holding_reg("getDumpingMode", "setDumpingMode")
-- check if any calls faulted return unit.interface(), false
local faulted = turbine.__p_is_faulted()
turbine.__p_clear_fault()
turbine.__p_enable_afc()
return unit.interface(), faulted
end end
return turbinev_rtu return turbinev_rtu

View File

@@ -37,12 +37,7 @@ function modbus.new(rtu_dev, use_parallel_read)
end) end)
else else
readings[i], access_fault = rtu_dev.read_coil(addr) readings[i], access_fault = rtu_dev.read_coil(addr)
if access_fault then break end
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
end end
@@ -86,12 +81,7 @@ function modbus.new(rtu_dev, use_parallel_read)
end) end)
else else
readings[i], access_fault = rtu_dev.read_di(addr) readings[i], access_fault = rtu_dev.read_di(addr)
if access_fault then break end
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
end end
@@ -135,12 +125,7 @@ function modbus.new(rtu_dev, use_parallel_read)
end) end)
else else
readings[i], access_fault = rtu_dev.read_holding_reg(addr) readings[i], access_fault = rtu_dev.read_holding_reg(addr)
if access_fault then break end
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
end end
@@ -184,12 +169,7 @@ function modbus.new(rtu_dev, use_parallel_read)
end) end)
else else
readings[i], access_fault = rtu_dev.read_input_reg(addr) readings[i], access_fault = rtu_dev.read_input_reg(addr)
if access_fault then break end
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
end end

View File

@@ -16,14 +16,15 @@ local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data") local DataIndicator = require("graphics.elements.indicators.data")
local LED = require("graphics.elements.indicators.led") local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb") local RGBLED = require("graphics.elements.indicators.ledrgb")
local LINK_STATE = types.PANEL_LINK_STATE
local ALIGN = core.ALIGN local ALIGN = core.ALIGN
local cpair = core.cpair local cpair = core.cpair
local fp_label = style.fp_label
local ind_grn = style.ind_grn local ind_grn = style.ind_grn
local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC TANK", "IND MATRIX", "SPS", "SNA", "ENV DETECTOR" } local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC TANK", "IND MATRIX", "SPS", "SNA", "ENV DETECTOR" }
@@ -32,7 +33,9 @@ local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC
---@param panel graphics_element main displaybox ---@param panel graphics_element main displaybox
---@param units table unit list ---@param units table unit list
local function init(panel, units) local function init(panel, units)
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} local disabled_fg = style.fp.disabled_fg
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
-- --
-- system indicators -- system indicators
@@ -48,12 +51,43 @@ local function init(panel, units)
heartbeat.register(databus.ps, "heartbeat", heartbeat.update) heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
local modem = LED{parent=system,label="MODEM",colors=ind_grn} local modem = LED{parent=system,label="MODEM",colors=ind_grn}
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
network.update(types.PANEL_LINK_STATE.DISCONNECTED) if not style.colorblind then
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}}
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
network.register(databus.ps, "link_state", network.update)
else
local nt_lnk = LEDPair{parent=system,label="NT LINKED",off=style.ind_bkg,c1=colors.red,c2=colors.green}
local nt_ver = LEDPair{parent=system,label="NT VERSION",off=style.ind_bkg,c1=colors.red,c2=colors.green}
nt_lnk.register(databus.ps, "link_state", function (state)
local value = 2
if state == LINK_STATE.DISCONNECTED then
value = 1
elseif state == LINK_STATE.LINKED then
value = 3
end
nt_lnk.update(value)
end)
nt_ver.register(databus.ps, "link_state", function (state)
local value = 3
if state == LINK_STATE.BAD_VERSION then
value = 2
elseif state == LINK_STATE.DISCONNECTED then
value = 1
end
nt_ver.update(value)
end)
end
system.line_break() system.line_break()
modem.register(databus.ps, "has_modem", modem.update) modem.register(databus.ps, "has_modem", modem.update)
network.register(databus.ps, "link_state", network.update)
local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn} local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn}
local rt_comm = LED{parent=system,label="RT COMMS",colors=ind_grn} local rt_comm = LED{parent=system,label="RT COMMS",colors=ind_grn}
@@ -64,17 +98,17 @@ local function init(panel, units)
---@diagnostic disable-next-line: undefined-field ---@diagnostic disable-next-line: undefined-field
local comp_id = util.sprintf("(%d)", os.getComputerID()) local comp_id = util.sprintf("(%d)", os.getComputerID())
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=fp_label} TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=disabled_fg}
TextBox{parent=system,x=1,y=14,text="SPEAKERS",height=1,width=8,fg_bg=style.label} TextBox{parent=system,x=1,y=14,text="SPEAKERS",height=1,width=8,fg_bg=style.fp.text_fg}
local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=cpair(colors.gray,colors.white)} local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box}
speaker_count.register(databus.ps, "speaker_count", speaker_count.update) speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
-- --
-- about label -- about label
-- --
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=fp_label} local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg}
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1} local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1} local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
@@ -116,7 +150,7 @@ local function init(panel, units)
-- assignment (unit # or facility) -- assignment (unit # or facility)
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor) local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,height=1,fg_bg=fp_label} TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,height=1,fg_bg=disabled_fg}
end end
end end

View File

@@ -2,47 +2,39 @@
-- Graphics Style Options -- Graphics Style Options
-- --
local core = require("graphics.core") local core = require("graphics.core")
local themes = require("graphics.themes")
---@class rtu_style
local style = {} local style = {}
local cpair = core.cpair local cpair = core.cpair
-- GLOBAL -- style.theme = themes.sandstone
style.fp = themes.get_fp_style(style.theme)
-- remap global colors style.colorblind = false
colors.ivory = colors.pink
colors.yellow_hc = colors.purple
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
style.root = cpair(colors.black, colors.ivory)
style.header = cpair(colors.black, colors.lightGray)
style.colors = {
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
-- { c = colors.white, hex = 0xdcd9ca },
{ c = colors.lightGray, hex = 0xb1b8b3 },
{ c = colors.gray, hex = 0x575757 },
-- { c = colors.black, hex = 0x191919 },
{ c = colors.brown, hex = 0x672223 } -- RED OFF
}
-- COMMON COLOR PAIRS --
style.fp_label = cpair(colors.lightGray, colors.ivory)
style.ind_grn = cpair(colors.green, colors.green_off) style.ind_grn = cpair(colors.green, colors.green_off)
-- set theme per configuration
---@param fp FP_THEME front panel theme
---@param color_mode COLOR_MODE the color mode to use
function style.set_theme(fp, color_mode)
if fp == themes.FP_THEME.SANDSTONE then
style.theme = themes.sandstone
elseif fp == themes.FP_THEME.BASALT then
style.theme = themes.basalt
end
style.fp = themes.get_fp_style(style.theme)
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
if color_mode == themes.COLOR_MODE.STANDARD or color_mode == themes.COLOR_MODE.BLUE_IND then
style.ind_bkg = colors.gray
else
style.ind_bkg = colors.black
end
end
return style return style

View File

@@ -19,11 +19,16 @@ local ui = {
-- try to start the UI -- try to start the UI
---@param units table RTU units ---@param units table RTU units
---@param theme FP_THEME front panel theme
---@param color_mode COLOR_MODE color mode
---@return boolean success, any error_msg ---@return boolean success, any error_msg
function renderer.try_start_ui(units) function renderer.try_start_ui(units, theme, color_mode)
local status, msg = true, nil local status, msg = true, nil
if ui.display == nil then if ui.display == nil then
-- set theme
style.set_theme(theme, color_mode)
-- reset terminal -- reset terminal
term.setTextColor(colors.white) term.setTextColor(colors.white)
term.setBackgroundColor(colors.black) term.setBackgroundColor(colors.black)
@@ -31,13 +36,19 @@ function renderer.try_start_ui(units)
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
-- set overridden colors -- set overridden colors
for i = 1, #style.colors do for i = 1, #style.theme.colors do
term.setPaletteColor(style.colors[i].c, style.colors[i].hex) term.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
end
-- apply color mode
local c_mode_overrides = style.theme.color_modes[color_mode]
for i = 1, #c_mode_overrides do
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
end end
-- init front panel view -- init front panel view
status, msg = pcall(function () status, msg = pcall(function ()
ui.display = DisplayBox{window=term.current(),fg_bg=style.root} ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
panel_view(ui.display, units) panel_view(ui.display, units)
end) end)
@@ -65,9 +76,9 @@ function renderer.close_ui()
ui.display = nil ui.display = nil
-- restore colors -- restore colors
for i = 1, #style.colors do for i = 1, #style.theme.colors do
local r, g, b = term.nativePaletteColor(style.colors[i].c) local r, g, b = term.nativePaletteColor(style.theme.colors[i].c)
term.setPaletteColor(style.colors[i].c, r, g, b) term.setPaletteColor(style.theme.colors[i].c, r, g, b)
end end
-- reset terminal -- reset terminal

View File

@@ -5,6 +5,8 @@ local log = require("scada-common.log")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local themes = require("graphics.themes")
local databus = require("rtu.databus") local databus = require("rtu.databus")
local modbus = require("rtu.modbus") local modbus = require("rtu.modbus")
@@ -29,15 +31,20 @@ function rtu.load_config()
config.Redstone = settings.get("Redstone") config.Redstone = settings.get("Redstone")
config.SpeakerVolume = settings.get("SpeakerVolume") config.SpeakerVolume = settings.get("SpeakerVolume")
config.SVR_Channel = settings.get("SVR_Channel") config.SVR_Channel = settings.get("SVR_Channel")
config.RTU_Channel = settings.get("RTU_Channel") config.RTU_Channel = settings.get("RTU_Channel")
config.ConnTimeout = settings.get("ConnTimeout") config.ConnTimeout = settings.get("ConnTimeout")
config.TrustedRange = settings.get("TrustedRange") config.TrustedRange = settings.get("TrustedRange")
config.AuthKey = settings.get("AuthKey") config.AuthKey = settings.get("AuthKey")
config.LogMode = settings.get("LogMode") config.LogMode = settings.get("LogMode")
config.LogPath = settings.get("LogPath") config.LogPath = settings.get("LogPath")
config.LogDebug = settings.get("LogDebug") config.LogDebug = settings.get("LogDebug")
config.FrontPanelTheme = settings.get("FrontPanelTheme")
config.ColorMode = settings.get("ColorMode")
local cfv = util.new_validator() local cfv = util.new_validator()
cfv.assert_type_num(config.SpeakerVolume) cfv.assert_type_num(config.SpeakerVolume)
@@ -53,7 +60,7 @@ function rtu.load_config()
if type(config.AuthKey) == "string" then if type(config.AuthKey) == "string" then
local len = string.len(config.AuthKey) local len = string.len(config.AuthKey)
cfv.assert_eq(len == 0 or len >= 8, true) cfv.assert(len == 0 or len >= 8)
end end
cfv.assert_type_int(config.LogMode) cfv.assert_type_int(config.LogMode)
@@ -61,15 +68,22 @@ function rtu.load_config()
cfv.assert_type_str(config.LogPath) cfv.assert_type_str(config.LogPath)
cfv.assert_type_bool(config.LogDebug) cfv.assert_type_bool(config.LogDebug)
cfv.assert_type_int(config.FrontPanelTheme)
cfv.assert_range(config.FrontPanelTheme, 1, 2)
cfv.assert_type_int(config.ColorMode)
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
cfv.assert_type_table(config.Peripherals) cfv.assert_type_table(config.Peripherals)
cfv.assert_type_table(config.Redstone) cfv.assert_type_table(config.Redstone)
return cfv.valid() return cfv.valid()
end end
-- create a new RTU unit -- create a new RTU unit<br>
-- if this is for a PPM peripheral, auto fault clearing MUST stay enabled once access begins
---@nodiscard ---@nodiscard
function rtu.init_unit() ---@param device table|nil peripheral device, if applicable
function rtu.init_unit(device)
local self = { local self = {
discrete_inputs = {}, discrete_inputs = {},
coils = {}, coils = {},
@@ -80,12 +94,18 @@ function rtu.init_unit()
local insert = table.insert local insert = table.insert
local stub = function () log.warning("tried to call an RTU function stub") end
---@class rtu_device ---@class rtu_device
local public = {} local public = {}
---@class rtu ---@class rtu
local protected = {} local protected = {}
-- function to check if the peripheral (if exists) is faulted
local function _is_faulted() return false end
if device then _is_faulted = device.__p_is_faulted end
-- refresh IO count -- refresh IO count
local function _count_io() local function _count_io()
self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs }
@@ -97,13 +117,26 @@ function rtu.init_unit()
return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4] return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4]
end end
-- pass a function through or generate one to call a function by name from the device
---@param f function|string function or device function name
local function _as_func(f)
if type(f) == "string" then
local name = f
if device then
f = function (...) return device[name](...) end
else f = stub end
end
return f
end
-- discrete inputs: single bit read-only -- discrete inputs: single bit read-only
-- connect discrete input -- connect discrete input
---@param f function ---@param f function|string function or function name
---@return integer count count of discrete inputs ---@return integer count count of discrete inputs
function protected.connect_di(f) function protected.connect_di(f)
insert(self.discrete_inputs, { read = f }) insert(self.discrete_inputs, { read = _as_func(f) })
_count_io() _count_io()
return #self.discrete_inputs return #self.discrete_inputs
end end
@@ -112,19 +145,18 @@ function rtu.init_unit()
---@param di_addr integer ---@param di_addr integer
---@return any value, boolean access_fault ---@return any value, boolean access_fault
function public.read_di(di_addr) function public.read_di(di_addr)
ppm.clear_fault()
local value = self.discrete_inputs[di_addr].read() local value = self.discrete_inputs[di_addr].read()
return value, ppm.is_faulted() return value, _is_faulted()
end end
-- coils: single bit read-write -- coils: single bit read-write
-- connect coil -- connect coil
---@param f_read function ---@param f_read function|string function or function name
---@param f_write function ---@param f_write function|string function or function name
---@return integer count count of coils ---@return integer count count of coils
function protected.connect_coil(f_read, f_write) function protected.connect_coil(f_read, f_write)
insert(self.coils, { read = f_read, write = f_write }) insert(self.coils, { read = _as_func(f_read), write = _as_func(f_write) })
_count_io() _count_io()
return #self.coils return #self.coils
end end
@@ -133,9 +165,8 @@ function rtu.init_unit()
---@param coil_addr integer ---@param coil_addr integer
---@return any value, boolean access_fault ---@return any value, boolean access_fault
function public.read_coil(coil_addr) function public.read_coil(coil_addr)
ppm.clear_fault()
local value = self.coils[coil_addr].read() local value = self.coils[coil_addr].read()
return value, ppm.is_faulted() return value, _is_faulted()
end end
-- write coil -- write coil
@@ -143,18 +174,17 @@ function rtu.init_unit()
---@param value any ---@param value any
---@return boolean access_fault ---@return boolean access_fault
function public.write_coil(coil_addr, value) function public.write_coil(coil_addr, value)
ppm.clear_fault()
self.coils[coil_addr].write(value) self.coils[coil_addr].write(value)
return ppm.is_faulted() return _is_faulted()
end end
-- input registers: multi-bit read-only -- input registers: multi-bit read-only
-- connect input register -- connect input register
---@param f function ---@param f function|string function or function name
---@return integer count count of input registers ---@return integer count count of input registers
function protected.connect_input_reg(f) function protected.connect_input_reg(f)
insert(self.input_regs, { read = f }) insert(self.input_regs, { read = _as_func(f) })
_count_io() _count_io()
return #self.input_regs return #self.input_regs
end end
@@ -163,19 +193,18 @@ function rtu.init_unit()
---@param reg_addr integer ---@param reg_addr integer
---@return any value, boolean access_fault ---@return any value, boolean access_fault
function public.read_input_reg(reg_addr) function public.read_input_reg(reg_addr)
ppm.clear_fault()
local value = self.input_regs[reg_addr].read() local value = self.input_regs[reg_addr].read()
return value, ppm.is_faulted() return value, _is_faulted()
end end
-- holding registers: multi-bit read-write -- holding registers: multi-bit read-write
-- connect holding register -- connect holding register
---@param f_read function ---@param f_read function|string function or function name
---@param f_write function ---@param f_write function|string function or function name
---@return integer count count of holding registers ---@return integer count count of holding registers
function protected.connect_holding_reg(f_read, f_write) function protected.connect_holding_reg(f_read, f_write)
insert(self.holding_regs, { read = f_read, write = f_write }) insert(self.holding_regs, { read = _as_func(f_read), write = _as_func(f_write) })
_count_io() _count_io()
return #self.holding_regs return #self.holding_regs
end end
@@ -184,9 +213,8 @@ function rtu.init_unit()
---@param reg_addr integer ---@param reg_addr integer
---@return any value, boolean access_fault ---@return any value, boolean access_fault
function public.read_holding_reg(reg_addr) function public.read_holding_reg(reg_addr)
ppm.clear_fault()
local value = self.holding_regs[reg_addr].read() local value = self.holding_regs[reg_addr].read()
return value, ppm.is_faulted() return value, _is_faulted()
end end
-- write holding register -- write holding register
@@ -194,9 +222,8 @@ function rtu.init_unit()
---@param value any ---@param value any
---@return boolean access_fault ---@return boolean access_fault
function public.write_holding_reg(reg_addr, value) function public.write_holding_reg(reg_addr, value)
ppm.clear_fault()
self.holding_regs[reg_addr].write(value) self.holding_regs[reg_addr].write(value)
return ppm.is_faulted() return _is_faulted()
end end
-- public RTU device access -- public RTU device access

View File

@@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
local sps_rtu = require("rtu.dev.sps_rtu") local sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.7.13" local RTU_VERSION = "v1.9.6"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
@@ -47,9 +47,13 @@ if not rtu.load_config() then
-- try to reconfigure (user action) -- try to reconfigure (user action)
local success, error = configure.configure(true) local success, error = configure.configure(true)
if success then if success then
assert(rtu.load_config(), "failed to load valid configuration") if not rtu.load_config() then
println("failed to load a valid configuration, please reconfigure")
return
end
else else
assert(success, "RTU configuration error: " .. error) println("configuration error: " .. error)
return
end end
end end
@@ -67,6 +71,7 @@ log.info("========================================")
println(">> RTU GATEWAY " .. RTU_VERSION .. " <<") println(">> RTU GATEWAY " .. RTU_VERSION .. " <<")
crash.set_env("rtu", RTU_VERSION) crash.set_env("rtu", RTU_VERSION)
crash.dbg_log_env()
---------------------------------------- ----------------------------------------
-- main application -- main application
@@ -338,7 +343,7 @@ local function main()
is_multiblock = true is_multiblock = true
formed = device.isFormed() formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then if formed == ppm.ACCESS_FAULT then
println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
return false return false
@@ -353,7 +358,7 @@ local function main()
is_multiblock = true is_multiblock = true
formed = device.isFormed() formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then if formed == ppm.ACCESS_FAULT then
println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
return false return false
@@ -373,7 +378,7 @@ local function main()
is_multiblock = true is_multiblock = true
formed = device.isFormed() formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then if formed == ppm.ACCESS_FAULT then
println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
return false return false
@@ -387,7 +392,7 @@ local function main()
is_multiblock = true is_multiblock = true
formed = device.isFormed() formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then if formed == ppm.ACCESS_FAULT then
println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
return false return false
@@ -401,7 +406,7 @@ local function main()
is_multiblock = true is_multiblock = true
formed = device.isFormed() formed = device.isFormed()
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then if formed == ppm.ACCESS_FAULT then
println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
return false return false
@@ -467,7 +472,8 @@ local function main()
for_message = util.c("reactor ", for_reactor) for_message = util.c("reactor ", for_reactor)
end end
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message)) local index_str = util.trinary(index ~= nil, util.c(" [", index, "]"), "")
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ")", index_str, " for ", for_message))
rtu_unit.uid = #units rtu_unit.uid = #units
@@ -502,7 +508,7 @@ local function main()
if sys_config() then if sys_config() then
-- start UI -- start UI
local message local message
rtu_state.fp_ok, message = renderer.try_start_ui(units) rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode)
if not rtu_state.fp_ok then if not rtu_state.fp_ok then
println_ts(util.c("UI error: ", message)) println_ts(util.c("UI error: ", message))

View File

@@ -517,82 +517,23 @@ function threads.thread__unit_comms(smem, unit)
-- check if multiblock is still formed if this is a multiblock -- check if multiblock is still formed if this is a multiblock
if unit.is_multiblock and (util.time_ms() - last_f_check > 250) then if unit.is_multiblock and (util.time_ms() - last_f_check > 250) then
local is_formed = unit.device.isFormed()
last_f_check = util.time_ms() last_f_check = util.time_ms()
local is_formed = unit.device.isFormed()
if unit.formed == nil then if unit.formed == nil then
unit.formed = is_formed unit.formed = is_formed
if is_formed then unit.hw_state = UNIT_HW_STATE.OK end if is_formed then unit.hw_state = UNIT_HW_STATE.OK end
elseif not unit.formed then
unit.hw_state = UNIT_HW_STATE.UNFORMED
end end
if not unit.formed then unit.hw_state = UNIT_HW_STATE.UNFORMED end if (is_formed == true) and not unit.formed then
unit.hw_state = UNIT_HW_STATE.OK
if (not unit.formed) and is_formed then log.info(util.c(detail_name, " is now formed"))
-- newly re-formed rtu_comms.send_remounted(unit.uid)
local iface = ppm.get_iface(unit.device) elseif (is_formed == false) and unit.formed then
if iface then log.warning(util.c(detail_name, " is no longer formed"))
log.info(util.c("unmounting and remounting reformed RTU unit ", detail_name))
ppm.unmount(unit.device)
local type, device = ppm.mount(iface)
local faulted = false
if device ~= nil then
if type == "boilerValve" and unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
-- boiler multiblock
unit.device = device
unit.rtu, faulted = boilerv_rtu.new(device)
unit.formed = device.isFormed()
unit.modbus_io = modbus.new(unit.rtu, true)
elseif type == "turbineValve" and unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
-- turbine multiblock
unit.device = device
unit.rtu, faulted = turbinev_rtu.new(device)
unit.formed = device.isFormed()
unit.modbus_io = modbus.new(unit.rtu, true)
elseif type == "dynamicValve" and unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
-- dynamic tank multiblock
unit.device = device
unit.rtu, faulted = dynamicv_rtu.new(device)
unit.formed = device.isFormed()
unit.modbus_io = modbus.new(unit.rtu, true)
elseif type == "inductionPort" and unit.type == RTU_UNIT_TYPE.IMATRIX then
-- induction matrix multiblock
unit.device = device
unit.rtu, faulted = imatrix_rtu.new(device)
unit.formed = device.isFormed()
unit.modbus_io = modbus.new(unit.rtu, true)
elseif type == "spsPort" and unit.type == RTU_UNIT_TYPE.SPS then
-- SPS multiblock
unit.device = device
unit.rtu, faulted = sps_rtu.new(device)
unit.formed = device.isFormed()
unit.modbus_io = modbus.new(unit.rtu, true)
else
log.error("illegal remount of non-multiblock RTU or type change attempted for " .. short_name, true)
end
if unit.formed and faulted then
-- something is still wrong = can't mark as formed yet
unit.formed = false
unit.hw_state = UNIT_HW_STATE.UNFORMED
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
else
unit.hw_state = UNIT_HW_STATE.OK
rtu_comms.send_remounted(unit.uid)
end
local type_name = types.rtu_type_to_string(unit.type)
log.info(util.c("reconnected the ", type_name, " on interface ", unit.name))
else
-- fully lost the peripheral now :(
log.error(util.c(unit.name, " lost (failed reconnect)"))
end
else
log.error("failed to get interface of previously connected RTU unit " .. detail_name, true)
end
end end
unit.formed = is_formed unit.formed = is_formed

View File

@@ -16,8 +16,9 @@ local max_distance = nil
---@class comms ---@class comms
local comms = {} local comms = {}
-- protocol/data version (protocol/data independent changes tracked by util.lua version) -- protocol/data versions (protocol/data independent changes tracked by util.lua version)
comms.version = "2.4.4" comms.version = "2.5.1"
comms.api_version = "0.0.2"
---@enum PROTOCOL ---@enum PROTOCOL
local PROTOCOL = { local PROTOCOL = {
@@ -64,7 +65,9 @@ local CRDN_TYPE = {
FAC_CMD = 3, -- faility command FAC_CMD = 3, -- faility command
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs) UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
UNIT_STATUSES = 5, -- state of each of the reactor units UNIT_STATUSES = 5, -- state of each of the reactor units
UNIT_CMD = 6 -- command a reactor unit UNIT_CMD = 6, -- command a reactor unit
API_GET_FAC = 7, -- API: get all the facility data
API_GET_UNIT = 8 -- API: get reactor unit data
} }
---@enum ESTABLISH_ACK ---@enum ESTABLISH_ACK
@@ -72,7 +75,8 @@ local ESTABLISH_ACK = {
ALLOW = 0, -- link approved ALLOW = 0, -- link approved
DENY = 1, -- link denied DENY = 1, -- link denied
COLLISION = 2, -- link denied due to existing active link COLLISION = 2, -- link denied due to existing active link
BAD_VERSION = 3 -- link denied due to comms version mismatch BAD_VERSION = 3, -- link denied due to comms version mismatch
BAD_API_VERSION = 4 -- link denied due to api version mismatch
} }
---@enum DEVICE_TYPE device types for establish messages ---@enum DEVICE_TYPE device types for establish messages
@@ -93,7 +97,8 @@ local FAC_COMMAND = {
START = 2, -- start automatic process control START = 2, -- start automatic process control
ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units
SET_WASTE_MODE = 4, -- set automatic waste processing mode SET_WASTE_MODE = 4, -- set automatic waste processing mode
SET_PU_FB = 5 -- set plutonium fallback mode SET_PU_FB = 5, -- set plutonium fallback mode
SET_SPS_LP = 6 -- set SPS at low power mode
} }
---@enum UNIT_COMMAND ---@enum UNIT_COMMAND

View File

@@ -29,7 +29,7 @@ local annunc = {}
annunc.RCSFlowLow_H2O = -3.2 -- flow < -3.2 mB/s annunc.RCSFlowLow_H2O = -3.2 -- flow < -3.2 mB/s
annunc.RCSFlowLow_NA = -2.0 -- flow < -2.0 mB/s annunc.RCSFlowLow_NA = -2.0 -- flow < -2.0 mB/s
annunc.CoolantLevelLow = 0.4 -- fill < 40% annunc.CoolantLevelLow = 0.4 -- fill < 40%
annunc.ReactorTempHigh = 1000 -- temp > 1000K annunc.OpTempTolerance = 5 -- high temp if >= operational temp + X
annunc.ReactorHighDeltaT = 50 -- rate > 50K/s annunc.ReactorHighDeltaT = 50 -- rate > 50K/s
annunc.FuelLevelLow = 0.05 -- fill <= 5% annunc.FuelLevelLow = 0.05 -- fill <= 5%
annunc.WasteLevelHigh = 0.80 -- fill >= 80% annunc.WasteLevelHigh = 0.80 -- fill >= 80%
@@ -66,21 +66,48 @@ constants.ALARM_LIMITS = alarms
--#endregion --#endregion
--#region Supervisor Redstone Activation Thresholds
---@class _rs_threshold_constants
local rs = {}
rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW
rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH
constants.RS_THRESHOLDS = rs
--#endregion
--#region Supervisor Constants --#region Supervisor Constants
-- milliseconds until turbine flow is assumed to be stable enough to enable coolant checks -- milliseconds until coolant flow is assumed to be stable enough to enable certain coolant checks
constants.FLOW_STABILITY_DELAY_MS = 15000 constants.FLOW_STABILITY_DELAY_MS = 10000
-- Notes on Radiation -- Notes on Radiation
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h) -- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h) -- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
-- - damaging radiation 0.00006 Sv/h (60 uSv/h) -- - damaging radiation 0.00006 Sv/h (60 uSv/h)
constants.LOW_RADIATION = 0.00001 constants.LOW_RADIATION = 0.00001
constants.HAZARD_RADIATION = 0.00006 constants.HAZARD_RADIATION = 0.00006
constants.HIGH_RADIATION = 0.001 constants.HIGH_RADIATION = 0.001
constants.VERY_HIGH_RADIATION = 0.1 constants.VERY_HIGH_RADIATION = 0.1
constants.SEVERE_RADIATION = 8.0 constants.SEVERE_RADIATION = 8.0
constants.EXTREME_RADIATION = 100.0 constants.EXTREME_RADIATION = 100.0
--#endregion
--#region Mekanism Configuration Constants
---@class _mek_constants
local mek = {}
mek.BASE_BOIL_TEMP = 373.15 -- mekanism: HeatUtils.BASE_BOIL_TEMP
mek.JOULES_PER_MB = 1000000 -- mekanism: energyPerFissionFuel
mek.TURBINE_GAS_PER_TANK = 64000 -- mekanism: turbineGasPerTank
mek.TURBINE_DISPERSER_FLOW = 1280 -- mekanism: turbineDisperserGasFlow
mek.TURBINE_VENT_FLOW = 32000 -- mekanism: turbineVentGasFlow
constants.mek = mek
--#endregion --#endregion

View File

@@ -24,6 +24,21 @@ function crash.set_env(application, version)
ver = version ver = version
end end
-- log environment versions
---@param log_msg function log function to use
local function log_versions(log_msg)
log_msg(util.c("RUNTIME: ", _HOST))
log_msg(util.c("LUA VERSION: ", _VERSION))
log_msg(util.c("APPLICATION: ", app))
log_msg(util.c("FIRMWARE VERSION: ", ver))
log_msg(util.c("COMMS VERSION: ", comms.version))
if has_graphics then log_msg(util.c("GRAPHICS VERSION: ", core.version)) end
if has_lockbox then log_msg(util.c("LOCKBOX VERSION: ", lockbox.version)) end
end
-- when running with debug logs, log the useful information that the crash handler knows
function crash.dbg_log_env() log_versions(log.debug) end
-- handle a crash error -- handle a crash error
---@param error string error message ---@param error string error message
function crash.handler(error) function crash.handler(error)
@@ -31,13 +46,7 @@ function crash.handler(error)
log.info("=====> FATAL SOFTWARE FAULT <=====") log.info("=====> FATAL SOFTWARE FAULT <=====")
log.fatal(error) log.fatal(error)
log.info("----------------------------------") log.info("----------------------------------")
log.info(util.c("RUNTIME: ", _HOST)) log_versions(log.info)
log.info(util.c("LUA VERSION: ", _VERSION))
log.info(util.c("APPLICATION: ", app))
log.info(util.c("FIRMWARE VERSION: ", ver))
log.info(util.c("COMMS VERSION: ", comms.version))
if has_graphics then log.info(util.c("GRAPHICS VERSION: ", core.version)) end
if has_lockbox then log.info(util.c("LOCKBOX VERSION: ", lockbox.version)) end
log.info("----------------------------------") log.info("----------------------------------")
log.info(debug.traceback("--- begin debug trace ---", 1)) log.info(debug.traceback("--- begin debug trace ---", 1))
log.info("--- end debug trace ---") log.info("--- end debug trace ---")

View File

@@ -53,6 +53,11 @@ function network.init_mac(passkey)
return init_time return init_time
end end
-- de-initialize message authentication system
function network.deinit_mac()
c_eng.key, c_eng.hmac = nil, nil
end
-- generate HMAC of message -- generate HMAC of message
---@nodiscard ---@nodiscard
---@param message string initial value concatenated with ciphertext ---@param message string initial value concatenated with ciphertext

View File

@@ -9,7 +9,7 @@ local util = require("scada-common.util")
local ppm = {} local ppm = {}
local ACCESS_FAULT = nil ---@type nil local ACCESS_FAULT = nil ---@type nil
local UNDEFINED_FIELD = "undefined field" local UNDEFINED_FIELD = "__PPM_UNDEF_FIELD__"
local VIRTUAL_DEVICE_TYPE = "ppm_vdev" local VIRTUAL_DEVICE_TYPE = "ppm_vdev"
ppm.ACCESS_FAULT = ACCESS_FAULT ppm.ACCESS_FAULT = ACCESS_FAULT
@@ -51,11 +51,13 @@ local function peri_init(iface)
self.device = peripheral.wrap(iface) self.device = peripheral.wrap(iface)
end end
-- initialization process (re-map) -- create a protected version of a peripheral function call
---@nodiscard
for key, func in pairs(self.device) do ---@param key string function name
self.fault_counts[key] = 0 ---@param func function function
self.device[key] = function (...) ---@return function method protected version of the function
local function protect_peri_function(key, func)
return function (...)
local return_table = table.pack(pcall(func, ...)) local return_table = table.pack(pcall(func, ...))
local status = return_table[1] local status = return_table[1]
@@ -85,20 +87,24 @@ local function peri_init(iface)
count_str = " [" .. self.fault_counts[key] .. " total faults]" count_str = " [" .. self.fault_counts[key] .. " total faults]"
end end
log.error(util.c("PPM: protected ", key, "() -> ", result, count_str)) log.error(util.c("PPM: [@", iface, "] protected ", key, "() -> ", result, count_str))
end end
self.fault_counts[key] = self.fault_counts[key] + 1 self.fault_counts[key] = self.fault_counts[key] + 1
if result == "Terminated" then if result == "Terminated" then ppm_sys.terminate = true end
ppm_sys.terminate = true
end
return ACCESS_FAULT return ACCESS_FAULT, result
end end
end end
end end
-- initialization process (re-map)
for key, func in pairs(self.device) do
self.fault_counts[key] = 0
self.device[key] = protect_peri_function(key, func)
end
-- fault management & monitoring functions -- fault management & monitoring functions
local function clear_fault() self.faulted = false end local function clear_fault() self.faulted = false end
@@ -131,31 +137,44 @@ local function peri_init(iface)
local mt = { local mt = {
__index = function (_, key) __index = function (_, key)
-- this will continuously be counting calls here as faults -- try to find the function in case it was added (multiblock formed)
-- unlike other functions, faults here can't be cleared as it is just not defined local funcs = peripheral.wrap(iface)
if self.fault_counts[key] == nil then if (type(funcs) == "table") and (type(funcs[key]) == "function") then
-- add this function then return it
self.fault_counts[key] = 0 self.fault_counts[key] = 0
self.device[key] = protect_peri_function(key, funcs[key])
log.info(util.c("PPM: [@", iface, "] initialized previously undefined field ", key, "()"))
return self.device[key]
end end
-- function failed -- function still missing, return an undefined function handler
self.faulted = true -- note: code should avoid storing functions for multiblocks and instead try to index them again
self.last_fault = UNDEFINED_FIELD return (function ()
-- this will continuously be counting calls here as faults
if self.fault_counts[key] == nil then self.fault_counts[key] = 0 end
ppm_sys.faulted = true -- function failed
ppm_sys.last_fault = UNDEFINED_FIELD self.faulted = true
self.last_fault = UNDEFINED_FIELD
if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then ppm_sys.faulted = true
local count_str = "" ppm_sys.last_fault = UNDEFINED_FIELD
if self.fault_counts[key] > 0 then
count_str = " [" .. self.fault_counts[key] .. " total calls]" if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
local count_str = ""
if self.fault_counts[key] > 0 then
count_str = " [" .. self.fault_counts[key] .. " total calls]"
end
log.error(util.c("PPM: [@", iface, "] caught undefined function ", key, "()", count_str))
end end
log.error(util.c("PPM: caught undefined function ", key, "()", count_str)) self.fault_counts[key] = self.fault_counts[key] + 1
end
self.fault_counts[key] = self.fault_counts[key] + 1 return ACCESS_FAULT, UNDEFINED_FIELD
end)
return (function () return ACCESS_FAULT end)
end end
} }
@@ -300,6 +319,17 @@ function ppm.handle_unmount(iface)
return pm_type, pm_dev return pm_type, pm_dev
end end
-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready
function ppm.log_mounts()
for iface, mount in pairs(ppm_sys.mounts) do
log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")"))
end
if util.table_len(ppm_sys.mounts) == 0 then
log.warning("PPM: no devices had been found")
end
end
-- GENERAL ACCESSORS -- -- GENERAL ACCESSORS --
-- list all available peripherals -- list all available peripherals

View File

@@ -52,6 +52,8 @@ local IO_PORT = {
-- facility -- facility
F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm) F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm)
F_ALARM_ANY = 8, -- active high, any alarm regardless of priority F_ALARM_ANY = 8, -- active high, any alarm regardless of priority
F_MATRIX_LOW = 27, -- active high, induction matrix charge low
F_MATRIX_HIGH = 28, -- active high, induction matrix charge high
-- waste -- waste
WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route
@@ -75,17 +77,27 @@ local IO_PORT = {
-- unit outputs -- unit outputs
U_ALARM = 25, -- active high, unit alarm U_ALARM = 25, -- active high, unit alarm
U_EMER_COOL = 26 -- active low, emergency coolant control U_EMER_COOL = 26, -- active low, emergency coolant control
-- analog outputs --
-- facility
F_MATRIX_CHG = 29 -- analog charge level of the induction matrix
} }
rsio.IO_LVL = IO_LVL rsio.IO_LVL = IO_LVL
rsio.IO_DIR = IO_DIR rsio.IO_DIR = IO_DIR
rsio.IO_MODE = IO_MODE rsio.IO_MODE = IO_MODE
rsio.IO = IO_PORT rsio.IO = IO_PORT
rsio.NUM_PORTS = IO_PORT.U_EMER_COOL
rsio.NUM_PORTS = 29
rsio.NUM_DIG_PORTS = 28
rsio.NUM_ANA_PORTS = 1
-- self checks -- self checks
assert(rsio.NUM_PORTS == (rsio.NUM_DIG_PORTS + rsio.NUM_ANA_PORTS), "port counts inconsistent")
local dup_chk = {} local dup_chk = {}
for _, v in pairs(IO_PORT) do for _, v in pairs(IO_PORT) do
assert(dup_chk[v] ~= true, "duplicate in port list") assert(dup_chk[v] ~= true, "duplicate in port list")
@@ -96,64 +108,45 @@ assert(#dup_chk == rsio.NUM_PORTS, "port list malformed")
--#endregion --#endregion
--#region Utility Functions --#region Utility Functions and Attribute Tables
local PORT_NAMES = { local IO = IO_PORT
"F_SCRAM",
"F_ACK",
"R_SCRAM",
"R_RESET",
"R_ENABLE",
"U_ACK",
"F_ALARM",
"F_ALARM_ANY",
"WASTE_PU",
"WASTE_PO",
"WASTE_POPL",
"WASTE_AM",
"R_ACTIVE",
"R_AUTO_CTRL",
"R_SCRAMMED",
"R_AUTO_SCRAM",
"R_HIGH_DMG",
"R_HIGH_TEMP",
"R_LOW_COOLANT",
"R_EXCESS_HC",
"R_EXCESS_WS",
"R_INSUFF_FUEL",
"R_PLC_FAULT",
"R_PLC_TIMEOUT",
"U_ALARM",
"U_EMER_COOL"
}
-- list of all port names
local PORT_NAMES = {}
for k, v in pairs(IO) do PORT_NAMES[v] = k end
-- list of all port I/O modes
local MODES = { local MODES = {
IO_MODE.DIGITAL_IN, -- F_SCRAM [IO.F_SCRAM] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- F_ACK [IO.F_ACK] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- R_SCRAM [IO.R_SCRAM] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- R_RESET [IO.R_RESET] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- R_ENABLE [IO.R_ENABLE] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_IN, -- U_ACK [IO.U_ACK] = IO_MODE.DIGITAL_IN,
IO_MODE.DIGITAL_OUT, -- F_ALARM [IO.F_ALARM] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY [IO.F_ALARM_ANY] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_PU [IO.F_MATRIX_LOW] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_PO [IO.F_MATRIX_HIGH] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_POPL [IO.WASTE_PU] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- WASTE_AM [IO.WASTE_PO] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_ACTIVE [IO.WASTE_POPL] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL [IO.WASTE_AM] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED [IO.R_ACTIVE] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM [IO.R_AUTO_CTRL] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG [IO.R_SCRAMMED] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP [IO.R_AUTO_SCRAM] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT [IO.R_HIGH_DMG] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC [IO.R_HIGH_TEMP] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS [IO.R_LOW_COOLANT] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL [IO.R_EXCESS_HC] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT [IO.R_EXCESS_WS] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT [IO.R_INSUFF_FUEL] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT, -- U_ALARM [IO.R_PLC_FAULT] = IO_MODE.DIGITAL_OUT,
IO_MODE.DIGITAL_OUT -- U_EMER_COOL [IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
} }
assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect") assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect")
@@ -179,74 +172,51 @@ local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else retur
-- I/O mappings to I/O function and I/O mode -- I/O mappings to I/O function and I/O mode
local RS_DIO_MAP = { local RS_DIO_MAP = {
-- F_SCRAM [IO.F_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, [IO.F_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- F_ACK
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- R_SCRAM [IO.R_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, [IO.R_RESET] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- R_RESET [IO.R_ENABLE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- R_ENABLE
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- U_ACK [IO.U_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN },
-- F_ALARM [IO.F_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.F_ALARM_ANY] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- F_ALARM_ANY [IO.F_MATRIX_LOW] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.F_MATRIX_HIGH] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- WASTE_PU [IO.WASTE_PU] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, [IO.WASTE_PO] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_PO [IO.WASTE_POPL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, [IO.WASTE_AM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_POPL
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- WASTE_AM
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
-- R_ACTIVE [IO.R_ACTIVE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_AUTO_CTRL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_AUTO_CTRL [IO.R_SCRAMMED] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_AUTO_SCRAM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_SCRAMMED [IO.R_HIGH_DMG] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_HIGH_TEMP] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_AUTO_SCRAM [IO.R_LOW_COOLANT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_EXCESS_HC] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_HIGH_DMG [IO.R_EXCESS_WS] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_INSUFF_FUEL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_HIGH_TEMP [IO.R_PLC_FAULT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_LOW_COOLANT
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_EXCESS_HC
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_EXCESS_WS
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_INSUFF_FUEL
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_PLC_FAULT
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- R_PLC_TIMEOUT
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
-- U_ALARM [IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
{ _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
-- U_EMER_COOL
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
} }
assert(rsio.NUM_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect") assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
-- get the I/O direction of a port -- get the I/O direction of a port
---@nodiscard ---@nodiscard
---@param port IO_PORT ---@param port IO_PORT
---@return IO_DIR ---@return IO_DIR
function rsio.get_io_dir(port) function rsio.get_io_dir(port)
if rsio.is_valid_port(port) then return RS_DIO_MAP[port].mode if rsio.is_valid_port(port) then
return util.trinary(MODES[port] == IO_MODE.DIGITAL_OUT or MODES[port] == IO_MODE.ANALOG_OUT, IO_DIR.OUT, IO_DIR.IN)
else return IO_DIR.IN end else return IO_DIR.IN end
end end
@@ -310,6 +280,13 @@ end
--#region Digital I/O --#region Digital I/O
-- check if a port is digital
---@nodiscard
---@param port IO_PORT
function rsio.is_digital(port)
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.DIGITAL_IN or MODES[port] == IO_MODE.DIGITAL_OUT)
end
-- get digital I/O level reading from a redstone boolean input value -- get digital I/O level reading from a redstone boolean input value
---@nodiscard ---@nodiscard
---@param rs_value boolean raw value from redstone ---@param rs_value boolean raw value from redstone
@@ -330,7 +307,7 @@ function rsio.digital_write(level) return level == IO_LVL.HIGH end
---@param active boolean state to convert to logic level ---@param active boolean state to convert to logic level
---@return IO_LVL|false ---@return IO_LVL|false
function rsio.digital_write_active(port, active) function rsio.digital_write_active(port, active)
if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then if not rsio.is_digital(port) then
return false return false
else else
return RS_DIO_MAP[port]._out(active) return RS_DIO_MAP[port]._out(active)
@@ -343,9 +320,7 @@ end
---@param level IO_LVL logic level ---@param level IO_LVL logic level
---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided ---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided
function rsio.digital_is_active(port, level) function rsio.digital_is_active(port, level)
if not util.is_int(port) then if (not rsio.is_digital(port)) or level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
return nil
elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then
return nil return nil
else else
return RS_DIO_MAP[port]._in(level) return RS_DIO_MAP[port]._in(level)
@@ -356,6 +331,13 @@ end
--#region Analog I/O --#region Analog I/O
-- check if a port is analog
---@nodiscard
---@param port IO_PORT
function rsio.is_analog(port)
return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.ANALOG_IN or MODES[port] == IO_MODE.ANALOG_OUT)
end
-- read an analog value scaled from min to max -- read an analog value scaled from min to max
---@nodiscard ---@nodiscard
---@param rs_value number redstone reading (0 to 15) ---@param rs_value number redstone reading (0 to 15)
@@ -372,7 +354,7 @@ end
---@param value number value to write (from min to max range) ---@param value number value to write (from min to max range)
---@param min number minimum of range ---@param min number minimum of range
---@param max number maximum of range ---@param max number maximum of range
---@return number rs_value scaled redstone reading (0 to 15) ---@return integer rs_value scaled redstone reading (0 to 15)
function rsio.analog_write(value, min, max) function rsio.analog_write(value, min, max)
local scaled_value = (value - min) / (max - min) local scaled_value = (value - min) / (max - min)
return math.floor(scaled_value * 15) return math.floor(scaled_value * 15)

View File

@@ -22,7 +22,7 @@ local t_pack = table.pack
local util = {} local util = {}
-- scada-common version -- scada-common version
util.version = "1.1.14" util.version = "1.3.0"
util.TICK_TIME_S = 0.05 util.TICK_TIME_S = 0.05
util.TICK_TIME_MS = 50 util.TICK_TIME_MS = 50
@@ -181,8 +181,7 @@ function util.round(x) return math.floor(x + 0.5) end
-- get a new moving average object -- get a new moving average object
---@nodiscard ---@nodiscard
---@param length integer history length ---@param length integer history length
---@param default number value to fill history with for first call to compute() function util.mov_avg(length)
function util.mov_avg(length, default)
local data = {} local data = {}
local index = 1 local index = 1
local last_t = 0 ---@type number|nil local last_t = 0 ---@type number|nil
@@ -190,11 +189,15 @@ function util.mov_avg(length, default)
---@class moving_average ---@class moving_average
local public = {} local public = {}
-- reset all to a given value -- reset all to a given value, or clear all data if no value is given
---@param x number value ---@param x number? value
function public.reset(x) function public.reset(x)
index = 1
data = {} data = {}
for _ = 1, length do t_insert(data, x) end
if x then
for _ = 1, length do t_insert(data, x) end
end
end end
-- record a new value -- record a new value
@@ -214,12 +217,15 @@ function util.mov_avg(length, default)
---@nodiscard ---@nodiscard
---@return number average ---@return number average
function public.compute() function public.compute()
local sum = 0 if #data == 0 then return 0 end
for i = 1, length do sum = sum + data[i] end
return sum / length
end
public.reset(default) local sum = 0
for i = 1, #data do
sum = sum + data[i]
end
return sum / #data
end
return public return public
end end
@@ -284,11 +290,13 @@ function util.cancel_timer(timer) os.cancelTimer(timer) end
--#region PARALLELIZATION --#region PARALLELIZATION
-- protected sleep call so we still are in charge of catching termination -- protected sleep call so we still are in charge of catching termination<br>
---@param t integer seconds -- returns the result of pcall
---@param t number seconds
---@return boolean success, any result, any ...
--- EVENT_CONSUMER: this function consumes events --- EVENT_CONSUMER: this function consumes events
---@diagnostic disable-next-line: undefined-field ---@diagnostic disable-next-line: undefined-field
function util.psleep(t) pcall(os.sleep, t) end function util.psleep(t) return pcall(os.sleep, t) end
-- no-op to provide a brief pause (1 tick) to yield<br> -- no-op to provide a brief pause (1 tick) to yield<br>
--- EVENT_CONSUMER: this function consumes events --- EVENT_CONSUMER: this function consumes events

View File

@@ -7,6 +7,7 @@ local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
local themes = require("graphics.themes")
local DisplayBox = require("graphics.elements.displaybox") local DisplayBox = require("graphics.elements.displaybox")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
@@ -22,6 +23,8 @@ local RadioButton = require("graphics.elements.controls.radio_button")
local NumberField = require("graphics.elements.form.number_field") local NumberField = require("graphics.elements.form.number_field")
local TextField = require("graphics.elements.form.text_field") local TextField = require("graphics.elements.form.text_field")
local IndLight = require("graphics.elements.indicators.light")
local println = util.println local println = util.println
local tri = util.trinary local tri = util.trinary
@@ -32,7 +35,10 @@ local CENTER = core.ALIGN.CENTER
local RIGHT = core.ALIGN.RIGHT local RIGHT = core.ALIGN.RIGHT
-- changes to the config data/format to let the user know -- changes to the config data/format to let the user know
local changes = {} local changes = {
{ "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } },
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
}
---@class svr_configurator ---@class svr_configurator
local configurator = {} local configurator = {}
@@ -42,34 +48,25 @@ local style = {}
style.root = cpair(colors.black, colors.lightGray) style.root = cpair(colors.black, colors.lightGray)
style.header = cpair(colors.white, colors.gray) style.header = cpair(colors.white, colors.gray)
style.colors = { style.colors = themes.smooth_stone.colors
{ c = colors.red, hex = 0xdf4949 },
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xfffc79 },
{ c = colors.lime, hex = 0x80ff80 },
{ c = colors.green, hex = 0x4aee8a },
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee },
{ c = colors.pink, hex = 0xf26ba2 },
{ c = colors.magenta, hex = 0xf9488a },
{ c = colors.lightGray, hex = 0xcacaca },
{ c = colors.gray, hex = 0x575757 }
}
local bw_fg_bg = cpair(colors.black, colors.white) local bw_fg_bg = cpair(colors.black, colors.white)
local g_lg_fg_bg = cpair(colors.gray, colors.lightGray) local g_lg_fg_bg = cpair(colors.gray, colors.lightGray)
local nav_fg_bg = bw_fg_bg local nav_fg_bg = bw_fg_bg
local btn_act_fg_bg = cpair(colors.white, colors.gray) local btn_act_fg_bg = cpair(colors.white, colors.gray)
---@class _svr_cfg_tool_ctl
local tool_ctl = { local tool_ctl = {
ask_config = false, ask_config = false,
has_config = false, has_config = false,
viewing_config = false, viewing_config = false,
importing_legacy = false, importing_legacy = false,
jumped_to_color = false,
view_cfg = nil, ---@type graphics_element view_cfg = nil, ---@type graphics_element
color_cfg = nil, ---@type graphics_element
color_next = nil, ---@type graphics_element
color_apply = nil, ---@type graphics_element
settings_apply = nil, ---@type graphics_element settings_apply = nil, ---@type graphics_element
gen_summary = nil, ---@type function gen_summary = nil, ---@type function
@@ -94,6 +91,7 @@ local tmp_cfg = {
CoolingConfig = {}, CoolingConfig = {},
FacilityTankMode = 0, FacilityTankMode = 0,
FacilityTankDefs = {}, FacilityTankDefs = {},
ExtChargeIdling = false,
SVR_Channel = nil, ---@type integer SVR_Channel = nil, ---@type integer
PLC_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer
RTU_Channel = nil, ---@type integer RTU_Channel = nil, ---@type integer
@@ -108,6 +106,8 @@ local tmp_cfg = {
LogMode = 0, LogMode = 0,
LogPath = "", LogPath = "",
LogDebug = false, LogDebug = false,
FrontPanelTheme = 1,
ColorMode = 1
} }
---@class svr_config ---@class svr_config
@@ -121,6 +121,7 @@ local fields = {
{ "CoolingConfig", "Cooling Configuration", {} }, { "CoolingConfig", "Cooling Configuration", {} },
{ "FacilityTankMode", "Facility Tank Mode", 0 }, { "FacilityTankMode", "Facility Tank Mode", 0 },
{ "FacilityTankDefs", "Facility Tank Definitions", {} }, { "FacilityTankDefs", "Facility Tank Definitions", {} },
{ "ExtChargeIdling", "Extended Charge Idling", false },
{ "SVR_Channel", "SVR Channel", 16240 }, { "SVR_Channel", "SVR Channel", 16240 },
{ "PLC_Channel", "PLC Channel", 16241 }, { "PLC_Channel", "PLC Channel", 16241 },
{ "RTU_Channel", "RTU Channel", 16242 }, { "RTU_Channel", "RTU Channel", 16242 },
@@ -134,7 +135,9 @@ local fields = {
{ "AuthKey", "Facility Auth Key" , ""}, { "AuthKey", "Facility Auth Key" , ""},
{ "LogMode", "Log Mode", log.MODE.APPEND }, { "LogMode", "Log Mode", log.MODE.APPEND },
{ "LogPath", "Log Path", "/log.txt" }, { "LogPath", "Log Path", "/log.txt" },
{ "LogDebug","Log Debug Messages", false } { "LogDebug", "Log Debug Messages", false },
{ "FrontPanelTheme", "Front Panel Theme", themes.FP_THEME.SANDSTONE },
{ "ColorMode", "Color Mode", themes.COLOR_MODE.STANDARD }
} }
-- load data from the settings file -- load data from the settings file
@@ -164,11 +167,12 @@ local function config_view(display)
local svr_cfg = Div{parent=root_pane_div,x=1,y=1} local svr_cfg = Div{parent=root_pane_div,x=1,y=1}
local net_cfg = Div{parent=root_pane_div,x=1,y=1} local net_cfg = Div{parent=root_pane_div,x=1,y=1}
local log_cfg = Div{parent=root_pane_div,x=1,y=1} local log_cfg = Div{parent=root_pane_div,x=1,y=1}
local clr_cfg = Div{parent=root_pane_div,x=1,y=1}
local summary = Div{parent=root_pane_div,x=1,y=1} local summary = Div{parent=root_pane_div,x=1,y=1}
local changelog = Div{parent=root_pane_div,x=1,y=1} local changelog = Div{parent=root_pane_div,x=1,y=1}
local import_err = Div{parent=root_pane_div,x=1,y=1} local import_err = Div{parent=root_pane_div,x=1,y=1}
local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,svr_cfg,net_cfg,log_cfg,summary,changelog,import_err}} local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,svr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,import_err}}
-- Main Page -- Main Page
@@ -185,7 +189,7 @@ local function config_view(display)
tool_ctl.viewing_config = true tool_ctl.viewing_config = true
tool_ctl.gen_summary(settings_cfg) tool_ctl.gen_summary(settings_cfg)
tool_ctl.settings_apply.hide(true) tool_ctl.settings_apply.hide(true)
main_pane.set_value(5) main_pane.set_value(6)
end end
if fs.exists("/supervisor/config.lua") then if fs.exists("/supervisor/config.lua") then
@@ -196,10 +200,21 @@ local function config_view(display)
PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} PushButton{parent=main_page,x=2,y=y_start,min_width=18,text="Configure System",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.view_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end local function jump_color()
tool_ctl.jumped_to_color = true
tool_ctl.color_next.hide(true)
tool_ctl.color_apply.show()
main_pane.set_value(5)
end
PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
if not tool_ctl.has_config then
tool_ctl.view_cfg.disable()
tool_ctl.color_cfg.disable()
end
--#region Facility --#region Facility
@@ -209,8 +224,9 @@ local function config_view(display)
local svr_c_4 = Div{parent=svr_cfg,x=2,y=4,width=49} local svr_c_4 = Div{parent=svr_cfg,x=2,y=4,width=49}
local svr_c_5 = Div{parent=svr_cfg,x=2,y=4,width=49} local svr_c_5 = Div{parent=svr_cfg,x=2,y=4,width=49}
local svr_c_6 = Div{parent=svr_cfg,x=2,y=4,width=49} local svr_c_6 = Div{parent=svr_cfg,x=2,y=4,width=49}
local svr_c_7 = Div{parent=svr_cfg,x=2,y=4,width=49}
local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5,svr_c_6}} local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5,svr_c_6,svr_c_7}}
TextBox{parent=svr_cfg,x=1,y=2,height=1,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)} TextBox{parent=svr_cfg,x=1,y=2,height=1,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
@@ -316,7 +332,7 @@ local function config_view(display)
else else
tmp_cfg.FacilityTankMode = 0 tmp_cfg.FacilityTankMode = 0
tmp_cfg.FacilityTankDefs = {} tmp_cfg.FacilityTankDefs = {}
main_pane.set_value(3) svr_pane.set_value(7)
end end
end end
@@ -550,7 +566,7 @@ local function config_view(display)
local function submit_mode() local function submit_mode()
tmp_cfg.FacilityTankMode = tank_mode.get_value() tmp_cfg.FacilityTankMode = tank_mode.get_value()
main_pane.set_value(3) svr_pane.set_value(7)
end end
PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
@@ -564,6 +580,23 @@ local function config_view(display)
PushButton{parent=svr_c_6,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=svr_c_6,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=svr_c_7,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
TextBox{parent=svr_c_7,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
local ext_idling = CheckBox{parent=svr_c_7,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
local function back_from_idling()
svr_pane.set_value(util.trinary(tmp_cfg.FacilityTankMode == 0, 3, 5))
end
local function submit_idling()
tmp_cfg.ExtChargeIdling = ext_idling.get_value()
main_pane.set_value(3)
end
PushButton{parent=svr_c_7,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=svr_c_7,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion --#endregion
--#region Network --#region Network
@@ -721,10 +754,8 @@ local function config_view(display)
tmp_cfg.LogMode = mode.get_value() - 1 tmp_cfg.LogMode = mode.get_value() - 1
tmp_cfg.LogPath = path.get_value() tmp_cfg.LogPath = path.get_value()
tmp_cfg.LogDebug = en_dbg.get_value() tmp_cfg.LogDebug = en_dbg.get_value()
tool_ctl.gen_summary(tmp_cfg) tool_ctl.color_apply.hide(true)
tool_ctl.viewing_config = false tool_ctl.color_next.show()
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(5) main_pane.set_value(5)
else path_err.show() end else path_err.show() end
end end
@@ -734,6 +765,116 @@ local function config_view(display)
--#endregion --#endregion
--#region Color Options
local clr_c_1 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_2 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_3 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_c_4 = Div{parent=clr_cfg,x=2,y=4,width=49}
local clr_pane = MultiPane{parent=clr_cfg,x=1,y=4,panes={clr_c_1,clr_c_2,clr_c_3,clr_c_4}}
TextBox{parent=clr_cfg,x=1,y=2,height=1,text=" Color Configuration",fg_bg=cpair(colors.black,colors.magenta)}
TextBox{parent=clr_c_1,x=1,y=1,height=2,text="Here you can select the color theme for the front panel."}
TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg}
TextBox{parent=clr_c_1,x=1,y=7,height=1,text="Front Panel Theme"}
local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."}
TextBox{parent=clr_c_2,x=21,y=7,height=1,text="Preview"}
local _ = IndLight{parent=clr_c_2,x=21,y=8,label="Good",colors=cpair(colors.black,colors.green)}
_ = IndLight{parent=clr_c_2,x=21,y=9,label="Warning",colors=cpair(colors.black,colors.yellow)}
_ = IndLight{parent=clr_c_2,x=21,y=10,label="Bad",colors=cpair(colors.black,colors.red)}
local b_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.black,colors.black),hidden=true}
local g_off = IndLight{parent=clr_c_2,x=21,y=11,label="Off",colors=cpair(colors.gray,colors.gray),hidden=true}
local function recolor(value)
local c = themes.smooth_stone.color_modes[value]
if value == themes.COLOR_MODE.STANDARD or value == themes.COLOR_MODE.BLUE_IND then
b_off.hide()
g_off.show()
else
g_off.hide()
b_off.show()
end
if #c == 0 then
for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end
else
term.setPaletteColor(colors.green, c[1].hex)
term.setPaletteColor(colors.yellow, c[2].hex)
term.setPaletteColor(colors.red, c[3].hex)
end
end
TextBox{parent=clr_c_2,x=1,y=7,height=1,width=10,text="Color Mode"}
local c_mode = RadioButton{parent=clr_c_2,x=1,y=8,default=ini_cfg.ColorMode,options=themes.COLOR_MODE_NAMES,callback=recolor,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta}
TextBox{parent=clr_c_2,x=21,y=13,height=2,width=18,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg}
PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
local function back_from_colors()
main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4))
tool_ctl.jumped_to_color = false
recolor(1)
end
local function show_access()
clr_pane.set_value(2)
recolor(c_mode.get_value())
end
local function submit_colors()
tmp_cfg.FrontPanelTheme = fp_theme.get_value()
tmp_cfg.ColorMode = c_mode.get_value()
if tool_ctl.jumped_to_color then
settings.set("FrontPanelTheme", tmp_cfg.FrontPanelTheme)
settings.set("ColorMode", tmp_cfg.ColorMode)
if settings.save("/supervisor.settings") then
load_settings(settings_cfg, true)
load_settings(ini_cfg)
clr_pane.set_value(3)
else
clr_pane.set_value(4)
end
else
tool_ctl.gen_summary(tmp_cfg)
tool_ctl.viewing_config = false
tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show()
main_pane.set_value(6)
end
end
PushButton{parent=clr_c_1,x=1,y=14,text="\x1b Back",callback=back_from_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=clr_c_1,x=8,y=14,min_width=15,text="Accessibility",callback=show_access,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_next = PushButton{parent=clr_c_1,x=44,y=14,text="Next \x1a",callback=submit_colors,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply = PushButton{parent=clr_c_1,x=43,y=14,min_width=7,text="Apply",callback=submit_colors,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
tool_ctl.color_apply.hide(true)
local function c_go_home()
main_pane.set_value(1)
clr_pane.set_value(1)
end
TextBox{parent=clr_c_3,x=1,y=1,height=1,text="Settings saved!"}
PushButton{parent=clr_c_3,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_3,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=clr_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
PushButton{parent=clr_c_4,x=1,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
PushButton{parent=clr_c_4,x=44,y=14,min_width=6,text="Home",callback=c_go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Summary and Saving --#region Summary and Saving
local sum_c_1 = Div{parent=summary,x=2,y=4,width=49} local sum_c_1 = Div{parent=summary,x=2,y=4,width=49}
@@ -754,7 +895,7 @@ local function config_view(display)
tool_ctl.importing_legacy = false tool_ctl.importing_legacy = false
tool_ctl.settings_apply.show() tool_ctl.settings_apply.show()
else else
main_pane.set_value(4) main_pane.set_value(5)
end end
end end
@@ -787,6 +928,8 @@ local function config_view(display)
try_set(mode, ini_cfg.LogMode) try_set(mode, ini_cfg.LogMode)
try_set(path, ini_cfg.LogPath) try_set(path, ini_cfg.LogPath)
try_set(en_dbg, ini_cfg.LogDebug) try_set(en_dbg, ini_cfg.LogDebug)
try_set(fp_theme, ini_cfg.FrontPanelTheme)
try_set(c_mode, ini_cfg.ColorMode)
for i = 1, #ini_cfg.CoolingConfig do for i = 1, #ini_cfg.CoolingConfig do
local cfg, elems = ini_cfg.CoolingConfig[i], tool_ctl.cooling_elems[i] local cfg, elems = ini_cfg.CoolingConfig[i], tool_ctl.cooling_elems[i]
@@ -824,6 +967,7 @@ local function config_view(display)
main_pane.set_value(1) main_pane.set_value(1)
svr_pane.set_value(1) svr_pane.set_value(1)
net_pane.set_value(1) net_pane.set_value(1)
clr_pane.set_value(1)
sum_pane.set_value(1) sum_pane.set_value(1)
end end
@@ -887,7 +1031,7 @@ local function config_view(display)
if config.REACTOR_COOLING == nil or tmp_cfg.UnitCount ~= #config.REACTOR_COOLING then if config.REACTOR_COOLING == nil or tmp_cfg.UnitCount ~= #config.REACTOR_COOLING then
import_err_msg.set_value("Cooling configuration table length must match the number of units.") import_err_msg.set_value("Cooling configuration table length must match the number of units.")
main_pane.set_value(7) main_pane.set_value(8)
return return
end end
@@ -896,7 +1040,7 @@ local function config_view(display)
if type(cfg) ~= "table" then if type(cfg) ~= "table" then
import_err_msg.set_value("Cooling configuration for unit " .. i .. " must be a table.") import_err_msg.set_value("Cooling configuration for unit " .. i .. " must be a table.")
main_pane.set_value(7) main_pane.set_value(8)
return return
end end
@@ -907,14 +1051,14 @@ local function config_view(display)
if not (util.is_int(tmp_cfg.FacilityTankMode) and tmp_cfg.FacilityTankMode >= 0 and tmp_cfg.FacilityTankMode <= 8) then if not (util.is_int(tmp_cfg.FacilityTankMode) and tmp_cfg.FacilityTankMode >= 0 and tmp_cfg.FacilityTankMode <= 8) then
import_err_msg.set_value("Invalid tank mode present in config. FAC_TANK_MODE must be a number 0 through 8.") import_err_msg.set_value("Invalid tank mode present in config. FAC_TANK_MODE must be a number 0 through 8.")
main_pane.set_value(7) main_pane.set_value(8)
return return
end end
if config.FAC_TANK_MODE > 0 then if config.FAC_TANK_MODE > 0 then
if config.FAC_TANK_DEFS == nil or tmp_cfg.UnitCount ~= #config.FAC_TANK_DEFS then if config.FAC_TANK_DEFS == nil or tmp_cfg.UnitCount ~= #config.FAC_TANK_DEFS then
import_err_msg.set_value("Facility tank definitions table length must match the number of units when using facility tanks.") import_err_msg.set_value("Facility tank definitions table length must match the number of units when using facility tanks.")
main_pane.set_value(7) main_pane.set_value(8)
return return
end end
@@ -945,7 +1089,7 @@ local function config_view(display)
tool_ctl.gen_summary(tmp_cfg) tool_ctl.gen_summary(tmp_cfg)
sum_pane.set_value(1) sum_pane.set_value(1)
main_pane.set_value(5) main_pane.set_value(6)
tool_ctl.importing_legacy = true tool_ctl.importing_legacy = true
end end
@@ -976,6 +1120,10 @@ local function config_view(display)
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
elseif f[1] == "FrontPanelTheme" then
val = util.strval(themes.fp_theme_name(raw))
elseif f[1] == "ColorMode" then
val = util.strval(themes.color_mode_name(raw))
elseif f[1] == "CoolingConfig" and type(cfg.CoolingConfig) == "table" then elseif f[1] == "CoolingConfig" and type(cfg.CoolingConfig) == "table" then
val = "" val = ""

View File

@@ -64,7 +64,7 @@ function databus.tx_plc_rtt(reactor_id, rtt)
elseif rtt > WARN_RTT then elseif rtt > WARN_RTT then
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc) databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
else else
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green) databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green_hc)
end end
end end
@@ -95,7 +95,7 @@ function databus.tx_rtu_rtt(session_id, rtt)
elseif rtt > WARN_RTT then elseif rtt > WARN_RTT then
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc) databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
else else
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green) databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green_hc)
end end
end end
@@ -134,7 +134,7 @@ function databus.tx_crd_rtt(rtt)
elseif rtt > WARN_RTT then elseif rtt > WARN_RTT then
databus.ps.publish("crd_rtt_color", colors.yellow_hc) databus.ps.publish("crd_rtt_color", colors.yellow_hc)
else else
databus.ps.publish("crd_rtt_color", colors.green) databus.ps.publish("crd_rtt_color", colors.green_hc)
end end
end end
@@ -165,7 +165,7 @@ function databus.tx_pdg_rtt(session_id, rtt)
elseif rtt > WARN_RTT then elseif rtt > WARN_RTT then
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc) databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
else else
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green) databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green_hc)
end end
end end

View File

@@ -23,7 +23,7 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local WASTE_MODE = types.WASTE_MODE local WASTE_MODE = types.WASTE_MODE
local WASTE = types.WASTE_PRODUCT local WASTE = types.WASTE_PRODUCT
local IO = rsio.IO local IO = rsio.IO
local DTV_RTU_S_DATA = qtypes.DTV_RTU_S_DATA local DTV_RTU_S_DATA = qtypes.DTV_RTU_S_DATA
@@ -50,9 +50,9 @@ local START_STATUS = {
BLADE_MISMATCH = 2 BLADE_MISMATCH = 2
} }
local charge_Kp = 0.275 local charge_Kp = 0.15
local charge_Ki = 0.0 local charge_Ki = 0.0
local charge_Kd = 4.5 local charge_Kd = 0.6
local rate_Kp = 2.45 local rate_Kp = 2.45
local rate_Ki = 0.4825 local rate_Ki = 0.4825
@@ -63,9 +63,9 @@ local facility = {}
-- create a new facility management object -- create a new facility management object
---@nodiscard ---@nodiscard
---@param num_reactors integer number of reactor units ---@param config svr_config supervisor configuration
---@param cooling_conf sv_cooling_conf cooling configurations of reactor units ---@param cooling_conf sv_cooling_conf cooling configurations of reactor units
function facility.new(num_reactors, cooling_conf) function facility.new(config, cooling_conf)
local self = { local self = {
units = {}, units = {},
status_text = { "START UP", "initializing..." }, status_text = { "START UP", "initializing..." },
@@ -120,6 +120,8 @@ function facility.new(num_reactors, cooling_conf)
waste_product = WASTE.PLUTONIUM, waste_product = WASTE.PLUTONIUM,
current_waste_product = WASTE.PLUTONIUM, current_waste_product = WASTE.PLUTONIUM,
pu_fallback = false, pu_fallback = false,
sps_low_power = false,
disabled_sps = false,
-- alarm tones -- alarm tones
tone_states = {}, tone_states = {},
test_tone_set = false, test_tone_set = false,
@@ -128,14 +130,21 @@ function facility.new(num_reactors, cooling_conf)
test_alarm_states = {}, test_alarm_states = {},
-- statistics -- statistics
im_stat_init = false, im_stat_init = false,
avg_charge = util.mov_avg(3, 0.0), avg_charge = util.mov_avg(3), -- 3 seconds
avg_inflow = util.mov_avg(6, 0.0), avg_inflow = util.mov_avg(6), -- 3 seconds
avg_outflow = util.mov_avg(6, 0.0) avg_outflow = util.mov_avg(6), -- 3 seconds
-- induction matrix charge delta stats
avg_net = util.mov_avg(60), -- 60 seconds
imtx_last_capacity = 0,
imtx_last_charge = 0,
imtx_last_charge_t = 0,
-- track faulted induction matrix update times to reject
imtx_faulted_times = { 0, 0, 0 }
} }
-- create units -- create units
for i = 1, num_reactors do for i = 1, config.UnitCount do
table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BoilerCount, cooling_conf.r_cool[i].TurbineCount)) table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BoilerCount, cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling))
table.insert(self.group_map, 0) table.insert(self.group_map, 0)
end end
@@ -152,6 +161,8 @@ function facility.new(num_reactors, cooling_conf)
table.insert(self.test_tone_states, false) table.insert(self.test_tone_states, false)
end end
-- PRIVATE FUNCTIONS --
-- check if all auto-controlled units completed ramping -- check if all auto-controlled units completed ramping
---@nodiscard ---@nodiscard
local function _all_units_ramped() local function _all_units_ramped()
@@ -223,12 +234,20 @@ function facility.new(num_reactors, cooling_conf)
return unallocated, false return unallocated, false
end end
-- set idle state of all assigned reactors
---@param idle boolean idle state
local function _set_idling(idle)
for i = 1, #self.prio_defs do
for _, u in pairs(self.prio_defs[i]) do u.auto_set_idle(idle) end
end
end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
---@class facility ---@class facility
local public = {} local public = {}
-- ADD/LINK DEVICES -- --#region Add/Link Devices
-- link a redstone RTU session -- link a redstone RTU session
---@param rs_unit unit_session ---@param rs_unit unit_session
@@ -268,11 +287,9 @@ function facility.new(num_reactors, cooling_conf)
for _, v in pairs(self.rtu_list) do util.filter_table(v, function (s) return s.get_session_id() ~= session end) end for _, v in pairs(self.rtu_list) do util.filter_table(v, function (s) return s.get_session_id() ~= session end) end
end end
-- UPDATE -- --#endregion
-- supervisor sessions reporting the list of active RTU sessions --#region Update
---@param rtu_sessions table session list of all connected RTUs
function public.report_rtus(rtu_sessions) self.rtu_conn_count = #rtu_sessions end
-- update (iterate) the facility management -- update (iterate) the facility management
function public.update() function public.update()
@@ -292,23 +309,68 @@ function facility.new(num_reactors, cooling_conf)
-- calculate moving averages for induction matrix -- calculate moving averages for induction matrix
if self.induction[1] ~= nil then if self.induction[1] ~= nil then
local matrix = self.induction[1] ---@type unit_session local matrix = self.induction[1] ---@type unit_session
local db = matrix.get_db() ---@type imatrix_session_db local db = matrix.get_db() ---@type imatrix_session_db
charge_update = db.tanks.last_update local build_update = db.build.last_update
rate_update = db.state.last_update rate_update = db.state.last_update
charge_update = db.tanks.last_update
local has_data = build_update > 0 and rate_update > 0 and charge_update > 0
if matrix.is_faulted() then
-- a fault occured, cannot reliably update stats
has_data = false
self.im_stat_init = false
self.imtx_faulted_times = { build_update, rate_update, charge_update }
elseif not self.im_stat_init then
-- prevent operation with partially invalid data
-- all fields must have updated since the last fault
has_data = self.imtx_faulted_times[1] < build_update and
self.imtx_faulted_times[2] < rate_update and
self.imtx_faulted_times[3] < charge_update
end
if has_data then
local energy = util.joules_to_fe(db.tanks.energy)
local input = util.joules_to_fe(db.state.last_input)
local output = util.joules_to_fe(db.state.last_output)
if (charge_update > 0) and (rate_update > 0) then
if self.im_stat_init then if self.im_stat_init then
self.avg_charge.record(util.joules_to_fe(db.tanks.energy), charge_update) self.avg_charge.record(energy, charge_update)
self.avg_inflow.record(util.joules_to_fe(db.state.last_input), rate_update) self.avg_inflow.record(input, rate_update)
self.avg_outflow.record(util.joules_to_fe(db.state.last_output), rate_update) self.avg_outflow.record(output, rate_update)
if charge_update ~= self.imtx_last_charge_t then
local delta = (energy - self.imtx_last_charge) / (charge_update - self.imtx_last_charge_t)
self.imtx_last_charge = energy
self.imtx_last_charge_t = charge_update
-- if the capacity changed, toss out existing data
if db.build.max_energy ~= self.imtx_last_capacity then
self.imtx_last_capacity = db.build.max_energy
self.avg_net.reset()
else
self.avg_net.record(delta, charge_update)
end
end
else else
self.im_stat_init = true self.im_stat_init = true
self.avg_charge.reset(util.joules_to_fe(db.tanks.energy))
self.avg_inflow.reset(util.joules_to_fe(db.state.last_input)) self.avg_charge.reset(energy)
self.avg_outflow.reset(util.joules_to_fe(db.state.last_output)) self.avg_inflow.reset(input)
self.avg_outflow.reset(output)
self.avg_net.reset()
self.imtx_last_capacity = db.build.max_energy
self.imtx_last_charge = energy
self.imtx_last_charge_t = charge_update
end end
else
-- prevent use by control systems
rate_update = 0
charge_update = 0
end end
else else
self.im_stat_init = false self.im_stat_init = false
@@ -323,12 +385,13 @@ function facility.new(num_reactors, cooling_conf)
-- Run Process Control -- -- Run Process Control --
------------------------- -------------------------
--#region Process Control --#region
local avg_charge = self.avg_charge.compute() local avg_charge = self.avg_charge.compute()
local avg_inflow = self.avg_inflow.compute() local avg_inflow = self.avg_inflow.compute()
local avg_outflow = self.avg_outflow.compute()
local now = util.time_s() local now = os.clock()
local state_changed = self.mode ~= self.last_mode local state_changed = self.mode ~= self.last_mode
local next_mode = self.mode local next_mode = self.mode
@@ -390,6 +453,7 @@ function facility.new(num_reactors, cooling_conf)
-- disable reactors and disengage auto control -- disable reactors and disengage auto control
for _, u in pairs(self.prio_defs[i]) do for _, u in pairs(self.prio_defs[i]) do
u.disable() u.disable()
u.auto_set_idle(false)
u.auto_disengage() u.auto_disengage()
end end
end end
@@ -460,9 +524,12 @@ function facility.new(num_reactors, cooling_conf)
self.last_error = 0 self.last_error = 0
self.accumulator = 0 self.accumulator = 0
-- enabling idling on all assigned units
_set_idling(true)
self.status_text = { "CHARGE MODE", "running control loop" } self.status_text = { "CHARGE MODE", "running control loop" }
log.info("FAC: CHARGE mode starting PID control") log.info("FAC: CHARGE mode starting PID control")
elseif self.last_update ~= charge_update then elseif self.last_update < charge_update then
-- convert to kFE to make constants not microscopic -- convert to kFE to make constants not microscopic
local error = util.round((self.charge_setpoint - avg_charge) / 1000) / 1000 local error = util.round((self.charge_setpoint - avg_charge) / 1000) / 1000
@@ -475,9 +542,9 @@ function facility.new(num_reactors, cooling_conf)
local integral = self.accumulator local integral = self.accumulator
local derivative = (error - self.last_error) / (now - self.last_time) local derivative = (error - self.last_error) / (now - self.last_time)
local P = (charge_Kp * error) local P = charge_Kp * error
local I = (charge_Ki * integral) local I = charge_Ki * integral
local D = (charge_Kd * derivative) local D = charge_Kd * derivative
local output = P + I + D local output = P + I + D
@@ -486,7 +553,12 @@ function facility.new(num_reactors, cooling_conf)
self.saturated = output ~= out_c self.saturated = output ~= out_c
-- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }", if not config.ExtChargeIdling then
-- stop idling early if the output is zero, we are at or above the setpoint, and are not losing charge
_set_idling(not ((out_c == 0) and (error <= 0) and (avg_outflow <= 0)))
end
-- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }",
-- runtime, avg_charge, error, integral, output, out_c, P, I, D)) -- runtime, avg_charge, error, integral, output, out_c, P, I, D))
_allocate_burn_rate(out_c, true) _allocate_burn_rate(out_c, true)
@@ -531,7 +603,7 @@ function facility.new(num_reactors, cooling_conf)
self.status_text = { "GENERATION MODE", "running control loop" } self.status_text = { "GENERATION MODE", "running control loop" }
log.info("FAC: GEN_RATE process mode initial hold completed, starting PID control") log.info("FAC: GEN_RATE process mode initial hold completed, starting PID control")
end end
elseif self.last_update ~= rate_update then elseif self.last_update < rate_update then
-- convert to MFE (in rounded kFE) to make constants not microscopic -- convert to MFE (in rounded kFE) to make constants not microscopic
local error = util.round((self.gen_rate_setpoint - avg_inflow) / 1000) / 1000 local error = util.round((self.gen_rate_setpoint - avg_inflow) / 1000) / 1000
@@ -544,9 +616,9 @@ function facility.new(num_reactors, cooling_conf)
local integral = self.accumulator local integral = self.accumulator
local derivative = (error - self.last_error) / (now - self.last_time) local derivative = (error - self.last_error) / (now - self.last_time)
local P = (rate_Kp * error) local P = rate_Kp * error
local I = (rate_Ki * integral) local I = rate_Ki * integral
local D = (rate_Kd * derivative) local D = rate_Kd * derivative
-- velocity (rate) (derivative of charge level => rate) feed forward -- velocity (rate) (derivative of charge level => rate) feed forward
local FF = self.gen_rate_setpoint / self.charge_conversion local FF = self.gen_rate_setpoint / self.charge_conversion
@@ -597,13 +669,12 @@ function facility.new(num_reactors, cooling_conf)
-- Evaluate Automatic SCRAM -- -- Evaluate Automatic SCRAM --
------------------------------ ------------------------------
--#region Automatic SCRAM --#region
local astatus = self.ascram_status local astatus = self.ascram_status
if self.induction[1] ~= nil then if self.induction[1] ~= nil then
local matrix = self.induction[1] ---@type unit_session local db = self.induction[1].get_db() ---@type imatrix_session_db
local db = matrix.get_db() ---@type imatrix_session_db
-- clear matrix disconnected -- clear matrix disconnected
if astatus.matrix_dc then if astatus.matrix_dc then
@@ -727,6 +798,8 @@ function facility.new(num_reactors, cooling_conf)
-- Handle Redstone I/O -- -- Handle Redstone I/O --
------------------------- -------------------------
--#region
if #self.redstone > 0 then if #self.redstone > 0 then
-- handle facility SCRAM -- handle facility SCRAM
if self.io_ctl.digital_read(IO.F_SCRAM) then if self.io_ctl.digital_read(IO.F_SCRAM) then
@@ -754,12 +827,25 @@ function facility.new(num_reactors, cooling_conf)
self.io_ctl.digital_write(IO.F_ALARM, has_prio_alarm) self.io_ctl.digital_write(IO.F_ALARM, has_prio_alarm)
self.io_ctl.digital_write(IO.F_ALARM_ANY, has_any_alarm) self.io_ctl.digital_write(IO.F_ALARM_ANY, has_any_alarm)
-- update induction matrix related outputs
if self.induction[1] ~= nil then
local db = self.induction[1].get_db() ---@type imatrix_session_db
self.io_ctl.digital_write(IO.F_MATRIX_LOW, db.tanks.energy_fill < const.RS_THRESHOLDS.IMATRIX_CHARGE_LOW)
self.io_ctl.digital_write(IO.F_MATRIX_HIGH, db.tanks.energy_fill > const.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH)
self.io_ctl.analog_write(IO.F_MATRIX_CHG, db.tanks.energy_fill, 0, 1)
end
end end
--#endregion
---------------- ----------------
-- Unit Tasks -- -- Unit Tasks --
---------------- ----------------
--#region
local insufficent_po_rate = false local insufficent_po_rate = false
local need_emcool = false local need_emcool = false
@@ -780,9 +866,25 @@ function facility.new(num_reactors, cooling_conf)
end end
-- update waste product -- update waste product
if self.waste_product == WASTE.PLUTONIUM or (self.pu_fallback and insufficent_po_rate) then
self.current_waste_product = self.waste_product
if (not self.sps_low_power) and (self.waste_product == WASTE.ANTI_MATTER) and (self.induction[1] ~= nil) then
local db = self.induction[1].get_db() ---@type imatrix_session_db
if db.tanks.energy_fill >= 0.15 then
self.disabled_sps = false
elseif self.disabled_sps or ((db.tanks.last_update > 0) and (db.tanks.energy_fill < 0.1)) then
self.disabled_sps = true
self.current_waste_product = WASTE.POLONIUM
end
else
self.disabled_sps = false
end
if self.pu_fallback and insufficent_po_rate then
self.current_waste_product = WASTE.PLUTONIUM self.current_waste_product = WASTE.PLUTONIUM
else self.current_waste_product = self.waste_product end end
-- make sure dynamic tanks are allowing outflow if required -- make sure dynamic tanks are allowing outflow if required
-- set all, rather than trying to determine which is for which (simpler & safer) -- set all, rather than trying to determine which is for which (simpler & safer)
@@ -798,10 +900,14 @@ function facility.new(num_reactors, cooling_conf)
end end
end end
--#endregion
------------------------ ------------------------
-- Update Alarm Tones -- -- Update Alarm Tones --
------------------------ ------------------------
--#region
local allow_test = self.allow_testing and self.test_tone_set local allow_test = self.allow_testing and self.test_tone_set
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false } local alarms = { false, false, false, false, false, false, false, false, false, false, false, false }
@@ -888,6 +994,8 @@ function facility.new(num_reactors, cooling_conf)
self.test_tone_set = false self.test_tone_set = false
self.test_tone_reset = true self.test_tone_reset = true
end end
--#endregion
end end
-- call the update function of all units in the facility<br> -- call the update function of all units in the facility<br>
@@ -900,7 +1008,9 @@ function facility.new(num_reactors, cooling_conf)
end end
end end
-- COMMANDS -- --#endregion
--#region Commands
-- SCRAM all reactor units -- SCRAM all reactor units
function public.scram_all() function public.scram_all()
@@ -922,41 +1032,41 @@ function facility.new(num_reactors, cooling_conf)
function public.auto_stop() self.mode = PROCESS.INACTIVE end function public.auto_stop() self.mode = PROCESS.INACTIVE end
-- set automatic control configuration and start the process -- set automatic control configuration and start the process
---@param config coord_auto_config configuration ---@param auto_cfg coord_auto_config configuration
---@return table response ready state (successfully started) and current configuration (after updating) ---@return table response ready state (successfully started) and current configuration (after updating)
function public.auto_start(config) function public.auto_start(auto_cfg)
local charge_scaler = 1000000 -- convert MFE to FE local charge_scaler = 1000000 -- convert MFE to FE
local gen_scaler = 1000 -- convert kFE to FE local gen_scaler = 1000 -- convert kFE to FE
local ready = false local ready = false
-- load up current limits -- load up current limits
local limits = {} local limits = {}
for i = 1, num_reactors do for i = 1, config.UnitCount do
local u = self.units[i] ---@type reactor_unit local u = self.units[i] ---@type reactor_unit
limits[i] = u.get_control_inf().lim_br100 * 100 limits[i] = u.get_control_inf().lim_br100 * 100
end end
-- only allow changes if not running -- only allow changes if not running
if self.mode == PROCESS.INACTIVE then if self.mode == PROCESS.INACTIVE then
if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.GEN_RATE) then if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then
self.mode_set = config.mode self.mode_set = auto_cfg.mode
end end
if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then
self.burn_target = config.burn_target self.burn_target = auto_cfg.burn_target
end end
if (type(config.charge_target) == "number") and config.charge_target >= 0 then if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then
self.charge_setpoint = config.charge_target * charge_scaler self.charge_setpoint = auto_cfg.charge_target * charge_scaler
end end
if (type(config.gen_target) == "number") and config.gen_target >= 0 then if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then
self.gen_rate_setpoint = config.gen_target * gen_scaler self.gen_rate_setpoint = auto_cfg.gen_target * gen_scaler
end end
if (type(config.limits) == "table") and (#config.limits == num_reactors) then if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then
for i = 1, num_reactors do for i = 1, config.UnitCount do
local limit = config.limits[i] local limit = auto_cfg.limits[i]
if (type(limit) == "number") and (limit >= 0.1) then if (type(limit) == "number") and (limit >= 0.1) then
limits[i] = limit limits[i] = limit
@@ -988,13 +1098,15 @@ function facility.new(num_reactors, cooling_conf)
} }
end end
-- SETTINGS -- --#endregion
--#region Settings
-- set the automatic control group of a unit -- set the automatic control group of a unit
---@param unit_id integer unit ID ---@param unit_id integer unit ID
---@param group integer group ID or 0 for independent ---@param group integer group ID or 0 for independent
function public.set_group(unit_id, group) function public.set_group(unit_id, group)
if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= num_reactors) and self.mode == PROCESS.INACTIVE then if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= config.UnitCount) and self.mode == PROCESS.INACTIVE then
-- remove from old group if previously assigned -- remove from old group if previously assigned
local old_group = self.group_map[unit_id] local old_group = self.group_map[unit_id]
if old_group ~= 0 then if old_group ~= 0 then
@@ -1029,7 +1141,17 @@ function facility.new(num_reactors, cooling_conf)
return self.pu_fallback return self.pu_fallback
end end
-- DIAGNOSTIC TESTING -- -- enable/disable SPS at low power
---@param enabled boolean requested state
---@return boolean enabled newly set value
function public.set_sps_low_power(enabled)
self.sps_low_power = enabled == true
return self.sps_low_power
end
--#endregion
--#region Diagnostic Testing
-- attempt to set a test tone state -- attempt to set a test tone state
---@param id TONE|0 tone ID or 0 to disable all ---@param id TONE|0 tone ID or 0 to disable all
@@ -1069,7 +1191,9 @@ function facility.new(num_reactors, cooling_conf)
return self.allow_testing, self.test_alarm_states return self.allow_testing, self.test_alarm_states
end end
-- READ STATES/PROPERTIES -- --#endregion
--#region Read States/Properties
-- get current alarm tone on/off states -- get current alarm tone on/off states
---@nodiscard ---@nodiscard
@@ -1129,7 +1253,8 @@ function facility.new(num_reactors, cooling_conf)
self.status_text[2], self.status_text[2],
self.group_map, self.group_map,
self.current_waste_product, self.current_waste_product,
(self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM) self.pu_fallback and (self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM),
self.disabled_sps
} }
end end
@@ -1145,15 +1270,21 @@ function facility.new(num_reactors, cooling_conf)
status.power = { status.power = {
self.avg_charge.compute(), self.avg_charge.compute(),
self.avg_inflow.compute(), self.avg_inflow.compute(),
self.avg_outflow.compute() self.avg_outflow.compute(),
0
} }
-- status of induction matricies (including tanks) -- status of induction matricies (including tanks)
status.induction = {} status.induction = {}
for i = 1, #self.induction do for i = 1, #self.induction do
local matrix = self.induction[i] ---@type unit_session local matrix = self.induction[i] ---@type unit_session
local db = matrix.get_db() ---@type imatrix_session_db local db = matrix.get_db() ---@type imatrix_session_db
status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks } status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks }
local fe_per_ms = self.avg_net.compute()
local remaining = util.joules_to_fe(util.trinary(fe_per_ms >= 0, db.tanks.energy_need, db.tanks.energy))
status.power[4] = remaining / fe_per_ms
end end
-- status of sps -- status of sps
@@ -1183,6 +1314,12 @@ function facility.new(num_reactors, cooling_conf)
return status return status
end end
--#endregion
-- supervisor sessions reporting the list of active RTU sessions
---@param rtu_sessions table session list of all connected RTUs
function public.report_rtus(rtu_sessions) self.rtu_conn_count = #rtu_sessions end
-- get the units in this facility -- get the units in this facility
---@nodiscard ---@nodiscard
function public.get_units() return self.units end function public.get_units() return self.units end

View File

@@ -17,31 +17,32 @@ local ALIGN = core.ALIGN
local cpair = core.cpair local cpair = core.cpair
local black_lg = style.black_lg
local lg_white = style.lg_white
-- create a pocket diagnostics list entry -- create a pocket diagnostics list entry
---@param parent graphics_element parent ---@param parent graphics_element parent
---@param id integer PDG session ID ---@param id integer PDG session ID
local function init(parent, id) local function init(parent, id)
local s_hi_box = style.theme.highlight_box
local label_fg = style.fp.label_fg
-- root div -- root div
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true} local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.bw_fg_bg} local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
local ps_prefix = "pdg_" .. id .. "_" local ps_prefix = "pdg_" .. id .. "_"
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=black_lg} TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
local pdg_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=black_lg,nav_active=cpair(colors.gray,colors.black)} local pdg_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=black_lg} TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
pdg_addr.register(databus.ps, ps_prefix .. "addr", pdg_addr.set_value) pdg_addr.register(databus.ps, ps_prefix .. "addr", pdg_addr.set_value)
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1} TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=lg_white} local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=label_fg}
pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value) pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value)
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1} TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_white} local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=lg_white} TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update) pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update)
pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor) pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor)

View File

@@ -17,35 +17,36 @@ local ALIGN = core.ALIGN
local cpair = core.cpair local cpair = core.cpair
local black_lg = style.black_lg
local lg_white = style.lg_white
-- create an RTU list entry -- create an RTU list entry
---@param parent graphics_element parent ---@param parent graphics_element parent
---@param id integer RTU session ID ---@param id integer RTU session ID
local function init(parent, id) local function init(parent, id)
local s_hi_box = style.theme.highlight_box
local label_fg = style.fp.label_fg
-- root div -- root div
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true} local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.bw_fg_bg} local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright}
local ps_prefix = "rtu_" .. id .. "_" local ps_prefix = "rtu_" .. id .. "_"
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=black_lg} TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
local rtu_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=black_lg,nav_active=cpair(colors.gray,colors.black)} local rtu_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)}
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=black_lg} TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
rtu_addr.register(databus.ps, ps_prefix .. "addr", rtu_addr.set_value) rtu_addr.register(databus.ps, ps_prefix .. "addr", rtu_addr.set_value)
TextBox{parent=entry,x=10,y=2,text="UNITS:",width=7,height=1} TextBox{parent=entry,x=10,y=2,text="UNITS:",width=7,height=1}
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.gray_white} local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.fp.label_d_fg}
unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value) unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value)
TextBox{parent=entry,x=21,y=2,text="FW:",width=3,height=1} TextBox{parent=entry,x=21,y=2,text="FW:",width=3,height=1}
local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,height=1,fg_bg=lg_white} local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,height=1,fg_bg=label_fg}
rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value) rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value)
TextBox{parent=entry,x=36,y=2,text="RTT:",width=4,height=1} TextBox{parent=entry,x=36,y=2,text="RTT:",width=4,height=1}
local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_white} local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=lg_white} TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update) rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update)
rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor) rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor)

View File

@@ -29,18 +29,18 @@ local ALIGN = core.ALIGN
local cpair = core.cpair local cpair = core.cpair
local bw_fg_bg = style.bw_fg_bg
local black_lg = style.black_lg
local lg_white = style.lg_white
local gry_wht = style.gray_white
local ind_grn = style.ind_grn local ind_grn = style.ind_grn
-- create new front panel view -- create new front panel view
---@param panel graphics_element main displaybox ---@param panel graphics_element main displaybox
local function init(panel) local function init(panel)
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} local s_hi_box = style.theme.highlight_box
local s_hi_bright = style.theme.highlight_box_bright
local label_fg = style.fp.label_fg
local label_d_fg = style.fp.label_d_fg
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,height=1,fg_bg=style.theme.header}
local page_div = Div{parent=panel,x=1,y=3} local page_div = Div{parent=panel,x=1,y=3}
@@ -66,13 +66,13 @@ local function init(panel)
---@diagnostic disable-next-line: undefined-field ---@diagnostic disable-next-line: undefined-field
local comp_id = util.sprintf("(%d)", os.getComputerID()) local comp_id = util.sprintf("(%d)", os.getComputerID())
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp_label} TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp.disabled_fg}
-- --
-- about footer -- about footer
-- --
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp_label} local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg}
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1} local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=ALIGN.LEFT,height=1}
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1} local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=ALIGN.LEFT,height=1}
@@ -90,25 +90,25 @@ local function init(panel)
for i = 1, supervisor.config.UnitCount do for i = 1, supervisor.config.UnitCount do
local ps_prefix = "plc_" .. i .. "_" local ps_prefix = "plc_" .. i .. "_"
local plc_entry = Div{parent=plc_list,height=3,fg_bg=bw_fg_bg} local plc_entry = Div{parent=plc_list,height=3,fg_bg=s_hi_bright}
TextBox{parent=plc_entry,x=1,y=1,text="",width=8,height=1,fg_bg=black_lg} TextBox{parent=plc_entry,x=1,y=1,text="",width=8,height=1,fg_bg=s_hi_box}
TextBox{parent=plc_entry,x=1,y=2,text="UNIT "..i,alignment=ALIGN.CENTER,width=8,height=1,fg_bg=black_lg} TextBox{parent=plc_entry,x=1,y=2,text="UNIT "..i,alignment=ALIGN.CENTER,width=8,height=1,fg_bg=s_hi_box}
TextBox{parent=plc_entry,x=1,y=3,text="",width=8,height=1,fg_bg=black_lg} TextBox{parent=plc_entry,x=1,y=3,text="",width=8,height=1,fg_bg=s_hi_box}
local conn = LED{parent=plc_entry,x=10,y=2,label="LINK",colors=ind_grn} local conn = LED{parent=plc_entry,x=10,y=2,label="LINK",colors=cpair(colors.green_hc,colors.green_off)}
conn.register(databus.ps, ps_prefix .. "conn", conn.update) conn.register(databus.ps, ps_prefix .. "conn", conn.update)
local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=gry_wht} local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=label_d_fg}
plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value) plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value)
TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3,height=1} TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3,height=1}
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,height=1,fg_bg=lg_white} local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,height=1,fg_bg=label_fg}
plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value) plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value)
TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4,height=1} TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4,height=1}
local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=lg_white} local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg}
TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,height=1,fg_bg=lg_white} TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update) plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update)
plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor) plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor)
@@ -124,29 +124,29 @@ local function init(panel)
-- coordinator page -- coordinator page
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true} local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=bw_fg_bg} local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=s_hi_bright}
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=ind_grn} local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green_hc,colors.green_off)}
crd_conn.register(databus.ps, "crd_conn", crd_conn.update) crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
TextBox{parent=crd_box,x=4,y=3,text="COMPUTER",width=8,height=1,fg_bg=gry_wht} TextBox{parent=crd_box,x=4,y=3,text="COMPUTER",width=8,height=1,fg_bg=label_d_fg}
local crd_addr = TextBox{parent=crd_box,x=13,y=3,text="---",width=5,height=1,fg_bg=gry_wht} local crd_addr = TextBox{parent=crd_box,x=13,y=3,text="---",width=5,height=1,fg_bg=label_d_fg}
crd_addr.register(databus.ps, "crd_addr", crd_addr.set_value) crd_addr.register(databus.ps, "crd_addr", crd_addr.set_value)
TextBox{parent=crd_box,x=22,y=2,text="FW:",width=3,height=1} TextBox{parent=crd_box,x=22,y=2,text="FW:",width=3,height=1}
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,height=1,fg_bg=lg_white} local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,height=1,fg_bg=label_fg}
crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value) crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value)
TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4,height=1} TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4,height=1}
local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=lg_white} local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg}
TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,height=1,fg_bg=lg_white} TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,height=1,fg_bg=label_fg}
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update) crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor) crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
-- pocket diagnostics page -- pocket diagnostics page
local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true} local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true}
local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
local _ = Div{parent=pdg_list,height=1,hidden=true} -- padding local _ = Div{parent=pdg_list,height=1,hidden=true} -- padding
-- assemble page panes -- assemble page panes
@@ -156,14 +156,14 @@ local function init(panel)
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
local tabs = { local tabs = {
{ name = "SVR", color = cpair(colors.black, colors.ivory) }, { name = "SVR", color = style.fp.text },
{ name = "PLC", color = cpair(colors.black, colors.ivory) }, { name = "PLC", color = style.fp.text },
{ name = "RTU", color = cpair(colors.black, colors.ivory) }, { name = "RTU", color = style.fp.text },
{ name = "CRD", color = cpair(colors.black, colors.ivory) }, { name = "CRD", color = style.fp.text },
{ name = "PKT", color = cpair(colors.black, colors.ivory) }, { name = "PKT", color = style.fp.text },
} }
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=bw_fg_bg} TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=style.theme.highlight_box_bright}
-- link RTU/PDG list management to PGI -- link RTU/PDG list management to PGI
pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry) pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)

View File

@@ -2,53 +2,33 @@
-- Graphics Style Options -- Graphics Style Options
-- --
local core = require("graphics.core") local core = require("graphics.core")
local themes = require("graphics.themes")
---@class svr_style
local style = {} local style = {}
local cpair = core.cpair local cpair = core.cpair
-- GLOBAL -- style.theme = themes.sandstone
style.fp = themes.get_fp_style(style.theme)
-- remap global colors style.colorblind = false
colors.ivory = colors.pink
colors.yellow_hc = colors.purple
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
style.root = cpair(colors.black, colors.ivory)
style.header = cpair(colors.black, colors.lightGray)
style.colors = {
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0008fe }, -- LCD BLUE
{ c = colors.purple, hex = 0xe3bc2a }, -- YELLOW HIGH CONTRAST
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
-- { c = colors.white, hex = 0xdcd9ca },
{ c = colors.lightGray, hex = 0xb1b8b3 },
{ c = colors.gray, hex = 0x575757 },
-- { c = colors.black, hex = 0x191919 },
{ c = colors.brown, hex = 0x672223 } -- RED OFF
}
-- COMMON COLOR PAIRS --
style.text_fg_bg = cpair(colors.black, colors.ivory)
style.bw_fg_bg = cpair(colors.black, colors.white)
style.fp_label = cpair(colors.lightGray, colors.ivory)
style.black_lg = cpair(colors.black, colors.lightGray)
style.lg_white = cpair(colors.lightGray, colors.white)
style.gray_white = cpair(colors.gray, colors.white)
style.ind_grn = cpair(colors.green, colors.green_off) style.ind_grn = cpair(colors.green, colors.green_off)
-- set theme per configuration
---@param fp FP_THEME front panel theme
---@param color_mode COLOR_MODE the color mode to use
function style.set_theme(fp, color_mode)
if fp == themes.FP_THEME.SANDSTONE then
style.theme = themes.sandstone
elseif fp == themes.FP_THEME.BASALT then
style.theme = themes.basalt
end
style.fp = themes.get_fp_style(style.theme)
style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK
end
return style return style

View File

@@ -19,11 +19,16 @@ local ui = {
} }
-- try to start the UI -- try to start the UI
---@param theme FP_THEME front panel theme
---@param color_mode COLOR_MODE color mode
---@return boolean success, any error_msg ---@return boolean success, any error_msg
function renderer.try_start_ui() function renderer.try_start_ui(theme, color_mode)
local status, msg = true, nil local status, msg = true, nil
if ui.display == nil then if ui.display == nil then
-- set theme
style.set_theme(theme, color_mode)
-- reset terminal -- reset terminal
term.setTextColor(colors.white) term.setTextColor(colors.white)
term.setBackgroundColor(colors.black) term.setBackgroundColor(colors.black)
@@ -31,13 +36,19 @@ function renderer.try_start_ui()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
-- set overridden colors -- set overridden colors
for i = 1, #style.colors do for i = 1, #style.theme.colors do
term.setPaletteColor(style.colors[i].c, style.colors[i].hex) term.setPaletteColor(style.theme.colors[i].c, style.theme.colors[i].hex)
end
-- apply color mode
local c_mode_overrides = style.theme.color_modes[color_mode]
for i = 1, #c_mode_overrides do
term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex)
end end
-- init front panel view -- init front panel view
status, msg = pcall(function () status, msg = pcall(function ()
ui.display = DisplayBox{window=term.current(),fg_bg=style.root} ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
panel_view(ui.display) panel_view(ui.display)
end) end)
@@ -70,9 +81,9 @@ function renderer.close_ui()
ui.display = nil ui.display = nil
-- restore colors -- restore colors
for i = 1, #style.colors do for i = 1, #style.theme.colors do
local r, g, b = term.nativePaletteColor(style.colors[i].c) local r, g, b = term.nativePaletteColor(style.theme.colors[i].c)
term.setPaletteColor(style.colors[i].c, r, g, b) term.setPaletteColor(style.theme.colors[i].c, r, g, b)
end end
-- reset terminal -- reset terminal

View File

@@ -17,9 +17,6 @@ local FAC_COMMAND = comms.FAC_COMMAND
local SV_Q_DATA = svqtypes.SV_Q_DATA local SV_Q_DATA = svqtypes.SV_Q_DATA
-- grace period in seconds for coordinator to finish UI draw to prevent timeout
local WATCHDOG_GRACE = 20.0
-- retry time constants in ms -- retry time constants in ms
-- local INITIAL_WAIT = 1500 -- local INITIAL_WAIT = 1500
local RETRY_PERIOD = 1000 local RETRY_PERIOD = 1000
@@ -273,6 +270,12 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
else else
log.debug(log_header .. "CRDN set pu fallback packet length mismatch") log.debug(log_header .. "CRDN set pu fallback packet length mismatch")
end end
elseif cmd == FAC_COMMAND.SET_SPS_LP then
if pkt.length == 2 then
_send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) })
else
log.debug(log_header .. "CRDN set sps low power packet length mismatch")
end
else else
log.debug(log_header .. "CRDN facility command unknown") log.debug(log_header .. "CRDN facility command unknown")
end end
@@ -360,15 +363,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil
-- check if a timer matches this session's watchdog -- check if a timer matches this session's watchdog
---@nodiscard ---@nodiscard
function public.check_wd(timer) function public.check_wd(timer)
local is_wd = self.conn_watchdog.is_timer(timer) and self.connected return self.conn_watchdog.is_timer(timer) and self.connected
-- if we are waiting for initial coordinator UI draw, don't close yet
if is_wd and (util.time_s() - self.establish_time) <= WATCHDOG_GRACE then
self.conn_watchdog.feed()
is_wd = false
end
return is_wd
end end
-- close the connection -- close the connection

View File

@@ -1,4 +1,5 @@
local comms = require("scada-common.comms") local comms = require("scada-common.comms")
local const = require("scada-common.constants")
local log = require("scada-common.log") local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local types = require("scada-common.types") local types = require("scada-common.types")
@@ -105,6 +106,8 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
formed = false, formed = false,
rps_tripped = false, rps_tripped = false,
rps_trip_cause = "ok", ---@type rps_trip_cause rps_trip_cause = "ok", ---@type rps_trip_cause
max_op_temp_H2O = 1200,
max_op_temp_Na = 1200,
---@class rps_status ---@class rps_status
rps_status = { rps_status = {
high_dmg = false, high_dmg = false,
@@ -138,11 +141,11 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
waste = 0, waste = 0,
waste_need = 0, waste_need = 0,
waste_fill = 0.0, waste_fill = 0.0,
ccool_type = "?", ccool_type = types.FLUID.EMPTY_GAS, ---@type fluid
ccool_amnt = 0, ccool_amnt = 0,
ccool_need = 0, ccool_need = 0,
ccool_fill = 0.0, ccool_fill = 0.0,
hcool_type = "?", hcool_type = types.FLUID.EMPTY_GAS, ---@type fluid
hcool_amnt = 0, hcool_amnt = 0,
hcool_need = 0, hcool_need = 0,
hcool_fill = 0.0 hcool_fill = 0.0
@@ -169,6 +172,21 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
---@class plc_session ---@class plc_session
local public = {} local public = {}
-- compute maximum expected operational temperatures for high temp warnings
local function _compute_op_temps()
local JOULES_PER_MB = const.mek.JOULES_PER_MB
local BASE_BOIL_TEMP = const.mek.BASE_BOIL_TEMP
local heat_cap = self.sDB.mek_struct.heat_cap
local max_burn = self.sDB.mek_struct.max_burn
self.sDB.max_op_temp_H2O = max_burn * 2 * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP
self.sDB.max_op_temp_Na = max_burn * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP
log.info(util.sprintf(log_header .. "computed maximum operational temperatures %.3fK (H2O) and %.3fK (Na)",
self.sDB.max_op_temp_H2O, self.sDB.max_op_temp_Na))
end
-- copy in the RPS status -- copy in the RPS status
---@param rps_status table ---@param rps_status table
local function _copy_rps_status(rps_status) local function _copy_rps_status(rps_status)
@@ -351,6 +369,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
local status = pcall(_copy_struct, pkt.data) local status = pcall(_copy_struct, pkt.data)
if status then if status then
-- copied in structure data OK -- copied in structure data OK
_compute_op_temps()
self.received_struct = true self.received_struct = true
out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id) out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id)
else else
@@ -639,6 +658,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
local cmd = message.message local cmd = message.message
if cmd == PLC_S_CMDS.ENABLE then if cmd == PLC_S_CMDS.ENABLE then
-- enable reactor -- enable reactor
self.acks.disable = true
if not self.auto_lock then if not self.auto_lock then
_send(RPLC_TYPE.RPS_ENABLE, {}) _send(RPLC_TYPE.RPS_ENABLE, {})
end end
@@ -695,6 +715,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
self.auto_cmd_token = 0 self.auto_cmd_token = 0
self.ramping_rate = true self.ramping_rate = true
self.acks.burn_rate = false self.acks.burn_rate = false
self.acks.disable = true
self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT
_send(RPLC_TYPE.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) _send(RPLC_TYPE.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate })
end end
@@ -702,13 +723,14 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f
elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then
-- set automatic burn rate -- set automatic burn rate
if self.auto_lock then if self.auto_lock then
cmd.val = math.floor(cmd.val * 100) / 100 -- round to 100ths place cmd.val = math.floor(cmd.val * 100) / 100 -- round to 100ths place
if cmd.val >= 0 and cmd.val <= self.sDB.mek_struct.max_burn then if cmd.val >= 0 and cmd.val <= self.sDB.mek_struct.max_burn then
self.auto_cmd_token = util.time_ms() self.auto_cmd_token = util.time_ms()
self.commanded_burn_rate = cmd.val self.commanded_burn_rate = cmd.val
-- this is only for manual control, only retry auto ramps -- this is only for manual control, only retry auto ramps
self.acks.burn_rate = not self.ramping_rate self.acks.burn_rate = not self.ramping_rate
self.acks.disable = true
self.retry_times.burn_rate_req = util.time() + INITIAL_AUTO_WAIT self.retry_times.burn_rate_req = util.time() + INITIAL_AUTO_WAIT
_send(RPLC_TYPE.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) _send(RPLC_TYPE.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token })

View File

@@ -2,6 +2,8 @@
-- Redstone RTU Session I/O Controller -- Redstone RTU Session I/O Controller
-- --
local rsio = require("scada-common.rsio")
local rsctl = {} local rsctl = {}
-- create a new redstone RTU I/O controller -- create a new redstone RTU I/O controller
@@ -16,7 +18,7 @@ function rsctl.new(redstone_rtus)
---@return boolean ---@return boolean
function public.is_connected(port) function public.is_connected(port)
for i = 1, #redstone_rtus do for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db local db = redstone_rtus[i].get_db() ---@type redstone_session_db
if db.io[port] ~= nil then return true end if db.io[port] ~= nil then return true end
end end
@@ -28,8 +30,8 @@ function rsctl.new(redstone_rtus)
---@param value boolean ---@param value boolean
function public.digital_write(port, value) function public.digital_write(port, value)
for i = 1, #redstone_rtus do for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then io.write(value) end if io ~= nil then io.write(value) end
end end
end end
@@ -40,12 +42,25 @@ function rsctl.new(redstone_rtus)
---@return boolean|nil ---@return boolean|nil
function public.digital_read(port) function public.digital_read(port)
for i = 1, #redstone_rtus do for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_dig_io|nil local io = db.io[port] ---@type rs_db_dig_io|nil
if io ~= nil then return io.read() end if io ~= nil then return io.read() end
end end
end end
-- write to an analog redstone port (applies to all RTUs)
---@param port IO_PORT
---@param value number value
---@param min number minimum value for scaling 0 to 15
---@param max number maximum value for scaling 0 to 15
function public.analog_write(port, value, min, max)
for i = 1, #redstone_rtus do
local db = redstone_rtus[i].get_db() ---@type redstone_session_db
local io = db.io[port] ---@type rs_db_ana_io|nil
if io ~= nil then io.write(rsio.analog_write(value, min, max)) end
end
end
return public return public
end end

View File

@@ -201,7 +201,7 @@ function svsessions.init(nic, fp_ok, config, cooling_conf)
self.nic = nic self.nic = nic
self.fp_ok = fp_ok self.fp_ok = fp_ok
self.config = config self.config = config
self.facility = facility.new(config.UnitCount, cooling_conf) self.facility = facility.new(config, cooling_conf)
end end
-- find an RTU session by the computer ID -- find an RTU session by the computer ID

View File

@@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v1.2.8" local SUPERVISOR_VERSION = "v1.3.11"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@@ -34,9 +34,13 @@ if not supervisor.load_config() then
-- try to reconfigure (user action) -- try to reconfigure (user action)
local success, error = configure.configure(true) local success, error = configure.configure(true)
if success then if success then
assert(supervisor.load_config(), "failed to load valid configuration") if not supervisor.load_config() then
println("failed to load a valid configuration, please reconfigure")
return
end
else else
assert(success, "supervisor configuration error: " .. error) println("configuration error: " .. error)
return
end end
end end
@@ -82,6 +86,7 @@ log.info("========================================")
println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<")
crash.set_env("supervisor", SUPERVISOR_VERSION) crash.set_env("supervisor", SUPERVISOR_VERSION)
crash.dbg_log_env()
---------------------------------------- ----------------------------------------
-- main application -- main application
@@ -114,7 +119,7 @@ local function main()
databus.tx_hw_modem(true) databus.tx_hw_modem(true)
-- start UI -- start UI
local fp_ok, message = renderer.try_start_ui() local fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
if not fp_ok then if not fp_ok then
println_ts(util.c("UI error: ", message)) println_ts(util.c("UI error: ", message))

View File

@@ -2,6 +2,8 @@ local comms = require("scada-common.comms")
local log = require("scada-common.log") local log = require("scada-common.log")
local util = require("scada-common.util") local util = require("scada-common.util")
local themes = require("graphics.themes")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local supervisor = {} local supervisor = {}
@@ -24,6 +26,7 @@ function supervisor.load_config()
config.CoolingConfig = settings.get("CoolingConfig") config.CoolingConfig = settings.get("CoolingConfig")
config.FacilityTankMode = settings.get("FacilityTankMode") config.FacilityTankMode = settings.get("FacilityTankMode")
config.FacilityTankDefs = settings.get("FacilityTankDefs") config.FacilityTankDefs = settings.get("FacilityTankDefs")
config.ExtChargeIdling = settings.get("ExtChargeIdling")
config.SVR_Channel = settings.get("SVR_Channel") config.SVR_Channel = settings.get("SVR_Channel")
config.PLC_Channel = settings.get("PLC_Channel") config.PLC_Channel = settings.get("PLC_Channel")
@@ -43,6 +46,9 @@ function supervisor.load_config()
config.LogPath = settings.get("LogPath") config.LogPath = settings.get("LogPath")
config.LogDebug = settings.get("LogDebug") config.LogDebug = settings.get("LogDebug")
config.FrontPanelTheme = settings.get("FrontPanelTheme")
config.ColorMode = settings.get("ColorMode")
local cfv = util.new_validator() local cfv = util.new_validator()
cfv.assert_type_int(config.UnitCount) cfv.assert_type_int(config.UnitCount)
@@ -53,6 +59,8 @@ function supervisor.load_config()
cfv.assert_type_int(config.FacilityTankMode) cfv.assert_type_int(config.FacilityTankMode)
cfv.assert_range(config.FacilityTankMode, 0, 8) cfv.assert_range(config.FacilityTankMode, 0, 8)
cfv.assert_type_bool(config.ExtChargeIdling)
cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.SVR_Channel)
cfv.assert_channel(config.PLC_Channel) cfv.assert_channel(config.PLC_Channel)
cfv.assert_channel(config.RTU_Channel) cfv.assert_channel(config.RTU_Channel)
@@ -73,7 +81,7 @@ function supervisor.load_config()
if type(config.AuthKey) == "string" then if type(config.AuthKey) == "string" then
local len = string.len(config.AuthKey) local len = string.len(config.AuthKey)
cfv.assert_eq(len == 0 or len >= 8, true) cfv.assert(len == 0 or len >= 8)
end end
cfv.assert_type_int(config.LogMode) cfv.assert_type_int(config.LogMode)
@@ -81,6 +89,11 @@ function supervisor.load_config()
cfv.assert_type_str(config.LogPath) cfv.assert_type_str(config.LogPath)
cfv.assert_type_bool(config.LogDebug) cfv.assert_type_bool(config.LogDebug)
cfv.assert_type_int(config.FrontPanelTheme)
cfv.assert_range(config.FrontPanelTheme, 1, 2)
cfv.assert_type_int(config.ColorMode)
cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES)
return cfv.valid() return cfv.valid()
end end

View File

@@ -8,9 +8,6 @@ local logic = require("supervisor.unitlogic")
local plc = require("supervisor.session.plc") local plc = require("supervisor.session.plc")
local rsctl = require("supervisor.session.rsctl") local rsctl = require("supervisor.session.rsctl")
---@class reactor_control_unit
local unit = {}
local WASTE_MODE = types.WASTE_MODE local WASTE_MODE = types.WASTE_MODE
local WASTE = types.WASTE_PRODUCT local WASTE = types.WASTE_PRODUCT
local ALARM = types.ALARM local ALARM = types.ALARM
@@ -55,17 +52,27 @@ local AISTATE = {
---@field id ALARM alarm ID ---@field id ALARM alarm ID
---@field tier integer alarm urgency tier (0 = highest) ---@field tier integer alarm urgency tier (0 = highest)
-- burn rate to idle at
local IDLE_RATE = 0.01
---@class reactor_control_unit
local unit = {}
-- create a new reactor unit -- create a new reactor unit
---@nodiscard ---@nodiscard
---@param reactor_id integer reactor unit number ---@param reactor_id integer reactor unit number
---@param num_boilers integer number of boilers expected ---@param num_boilers integer number of boilers expected
---@param num_turbines integer number of turbines expected ---@param num_turbines integer number of turbines expected
function unit.new(reactor_id, num_boilers, num_turbines) ---@param ext_idle boolean extended idling mode
function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
-- time (ms) to idle for auto idling
local IDLE_TIME = util.trinary(ext_idle, 60000, 10000)
---@class _unit_self ---@class _unit_self
local self = { local self = {
r_id = reactor_id, r_id = reactor_id,
plc_s = nil, ---@class plc_session_struct plc_s = nil, ---@type plc_session_struct
plc_i = nil, ---@class plc_session plc_i = nil, ---@type plc_session
num_boilers = num_boilers, num_boilers = num_boilers,
num_turbines = num_turbines, num_turbines = num_turbines,
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
@@ -77,13 +84,15 @@ function unit.new(reactor_id, num_boilers, num_turbines)
tanks = {}, tanks = {},
snas = {}, snas = {},
envd = {}, envd = {},
sna_prod_rate = 0,
-- redstone control -- redstone control
io_ctl = nil, ---@type rs_controller io_ctl = nil, ---@type rs_controller
valves = {}, ---@type unit_valves valves = {}, ---@type unit_valves
emcool_opened = false, emcool_opened = false,
-- auto control -- auto control
auto_engaged = false, auto_engaged = false,
auto_idle = false,
auto_idling = false,
auto_idle_start = 0,
auto_was_alarmed = false, auto_was_alarmed = false,
ramp_target_br100 = 0, ramp_target_br100 = 0,
-- state tracking -- state tracking
@@ -99,6 +108,8 @@ function unit.new(reactor_id, num_boilers, num_turbines)
status_text = { "UNKNOWN", "awaiting connection..." }, status_text = { "UNKNOWN", "awaiting connection..." },
-- logic for alarms -- logic for alarms
had_reactor = false, had_reactor = false,
turbine_flow_stable = false,
turbine_stability_data = {},
last_rate_change_ms = 0, last_rate_change_ms = 0,
---@type rps_status ---@type rps_status
last_rps_trips = { last_rps_trips = {
@@ -136,7 +147,8 @@ function unit.new(reactor_id, num_boilers, num_turbines)
}, },
damage = 0, damage = 0,
temp = 0, temp = 0,
waste = 0 waste = 0,
high_temp_lim = 1150
}, },
---@class alarm_monitors ---@class alarm_monitors
alarms = { alarms = {
@@ -252,11 +264,12 @@ function unit.new(reactor_id, num_boilers, num_turbines)
table.insert(self.db.annunciator.TurbineOverSpeed, false) table.insert(self.db.annunciator.TurbineOverSpeed, false)
table.insert(self.db.annunciator.GeneratorTrip, false) table.insert(self.db.annunciator.GeneratorTrip, false)
table.insert(self.db.annunciator.TurbineTrip, false) table.insert(self.db.annunciator.TurbineTrip, false)
table.insert(self.turbine_stability_data, { time_state = 0, time_tanks = 0, rotation = 1 })
end end
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
--#region time derivative utility functions --#region Time Derivative Utility Functions
-- compute a change with respect to time of the given value -- compute a change with respect to time of the given value
---@param key string value key ---@param key string value key
@@ -331,7 +344,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
--#endregion --#endregion
--#region redstone I/O --#region Redstone I/O
-- create a generic valve interface -- create a generic valve interface
---@nodiscard ---@nodiscard
@@ -398,8 +411,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
---@class reactor_unit ---@class reactor_unit
local public = {} local public = {}
-- ADD/LINK DEVICES -- --#region Add/Link Devices
--#region
-- link the PLC -- link the PLC
---@param plc_session plc_session_struct ---@param plc_session plc_session_struct
@@ -489,7 +501,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
--#endregion --#endregion
-- UPDATE SESSION -- --#region Update Session
-- update (iterate) this unit -- update (iterate) this unit
function public.update() function public.update()
@@ -532,6 +544,13 @@ function unit.new(reactor_id, num_boilers, num_turbines)
-- re-engage auto lock if it reconnected without it -- re-engage auto lock if it reconnected without it
if self.auto_engaged and not self.plc_i.is_auto_locked() then self.plc_i.auto_lock(true) end if self.auto_engaged and not self.plc_i.is_auto_locked() then self.plc_i.auto_lock(true) end
-- stop idling when completed
if self.auto_idling and (((util.time_ms() - self.auto_idle_start) > IDLE_TIME) or not self.auto_idle) then
log.info(util.c("UNIT ", self.r_id, ": completed idling period"))
self.auto_idling = false
self.plc_i.auto_set_burn(0, false)
end
end end
-- update deltas -- update deltas
@@ -557,8 +576,9 @@ function unit.new(reactor_id, num_boilers, num_turbines)
end end
end end
-- AUTO CONTROL OPERATIONS -- --#endregion
--#region
--#region Auto Control Operations
-- engage automatic control -- engage automatic control
function public.auto_engage() function public.auto_engage()
@@ -579,6 +599,23 @@ function unit.new(reactor_id, num_boilers, num_turbines)
end end
end end
-- set automatic control idling mode to change behavior when given a burn rate command of zero<br>
-- - enabling it will hold the reactor at 0.01 mB/t for a period when commanded zero before disabling
-- - disabling it will stop the reactor when commanded zero
---@param idle boolean true to enable, false to disable (and stop)
function public.auto_set_idle(idle)
if idle and not self.auto_idle then
self.auto_idling = false
self.auto_idle_start = 0
end
if idle ~= self.auto_idle then
log.debug(util.c("UNIT ", self.r_id, ": idling mode changed to ", idle))
end
self.auto_idle = idle
end
-- get the actual limit of this unit<br> -- get the actual limit of this unit<br>
-- if it is degraded or not ready, the limit will be 0 -- if it is degraded or not ready, the limit will be 0
---@nodiscard ---@nodiscard
@@ -598,7 +635,35 @@ function unit.new(reactor_id, num_boilers, num_turbines)
if self.auto_engaged then if self.auto_engaged then
if self.plc_i ~= nil then if self.plc_i ~= nil then
log.debug(util.c("UNIT ", self.r_id, ": commit br100 of ", self.db.control.br100, " with ramp set to ", ramp)) log.debug(util.c("UNIT ", self.r_id, ": commit br100 of ", self.db.control.br100, " with ramp set to ", ramp))
self.plc_i.auto_set_burn(self.db.control.br100 / 100, ramp)
local rate = self.db.control.br100 / 100
if self.auto_idle then
if rate <= IDLE_RATE then
if self.auto_idle_start == 0 then
self.auto_idling = true
self.auto_idle_start = util.time_ms()
log.info(util.c("UNIT ", self.r_id, ": started idling at ", IDLE_RATE, " mB/t"))
rate = IDLE_RATE
elseif (util.time_ms() - self.auto_idle_start) > IDLE_TIME then
if self.auto_idling then
self.auto_idling = false
log.info(util.c("UNIT ", self.r_id, ": completed idling period"))
end
else
log.debug(util.c("UNIT ", self.r_id, ": continuing idle at ", IDLE_RATE, " mB/t"))
rate = IDLE_RATE
end
else
self.auto_idling = false
self.auto_idle_start = 0
end
end
self.plc_i.auto_set_burn(rate, ramp)
if ramp then self.ramp_target_br100 = self.db.control.br100 end if ramp then self.ramp_target_br100 = self.db.control.br100 end
end end
end end
@@ -645,8 +710,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
--#endregion --#endregion
-- OPERATIONS -- --#region Operations
--#region
-- queue a command to disable the reactor -- queue a command to disable the reactor
function public.disable() function public.disable()
@@ -726,8 +790,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
--#endregion --#endregion
-- READ STATES/PROPERTIES -- --#region Read States/Properties
--#region
-- check if an alarm of at least a certain priority level is tripped -- check if an alarm of at least a certain priority level is tripped
---@nodiscard ---@nodiscard
@@ -857,13 +920,15 @@ function unit.new(reactor_id, num_boilers, num_turbines)
status.tanks[tank.get_device_idx()] = { tank.is_faulted(), db.formed, db.state, db.tanks } status.tanks[tank.get_device_idx()] = { tank.is_faulted(), db.formed, db.state, db.tanks }
end end
-- basic SNA statistical information -- SNA statistical information
local total_peak = 0 local total_peak, total_avail, total_out = 0, 0, 0
for i = 1, #self.snas do for i = 1, #self.snas do
local db = self.snas[i].get_db() ---@type sna_session_db local db = self.snas[i].get_db() ---@type sna_session_db
total_peak = total_peak + db.state.peak_production total_peak = total_peak + db.state.peak_production
total_avail = total_avail + db.state.production_rate
total_out = total_out + math.min(db.tanks.input.amount / 10, db.state.production_rate)
end end
status.sna = { #self.snas, public.get_sna_rate(), total_peak } status.sna = { #self.snas, total_peak, total_avail, total_out }
-- radiation monitors (environment detectors) -- radiation monitors (environment detectors)
status.envds = {} status.envds = {}
@@ -876,7 +941,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
return status return status
end end
-- get the current total [max] production rate is -- get the current total max production rate
---@nodiscard ---@nodiscard
---@return number total_avail_rate ---@return number total_avail_rate
function public.get_sna_rate() function public.get_sna_rate()

View File

@@ -39,6 +39,21 @@ local ALARM_LIMS = const.ALARM_LIMITS
---@class unit_logic_extension ---@class unit_logic_extension
local logic = {} local logic = {}
-- compute Mekanism's rotation rate for a turbine
---@param turbine turbinev_session_db
local function turbine_rotation(turbine)
local build = turbine.build
local inner_vol = build.steam_cap / const.mek.TURBINE_GAS_PER_TANK
local disp_rate = (build.dispersers * const.mek.TURBINE_DISPERSER_FLOW) * inner_vol
local vent_rate = build.vents * const.mek.TURBINE_VENT_FLOW
local max_rate = math.min(disp_rate, vent_rate)
local flow = math.min(max_rate, turbine.tanks.steam.amount)
return (flow * (turbine.tanks.steam.amount / build.steam_cap)) / max_rate
end
-- update the annunciator -- update the annunciator
---@param self _unit_self ---@param self _unit_self
function logic.update_annunciator(self) function logic.update_annunciator(self)
@@ -54,9 +69,7 @@ function logic.update_annunciator(self)
-- variables for boiler, or reactor if no boilers used -- variables for boiler, or reactor if no boilers used
local total_boil_rate = 0.0 local total_boil_rate = 0.0
------------- --#region Reactor
-- REACTOR --
-------------
annunc.AutoControl = self.auto_engaged annunc.AutoControl = self.auto_engaged
@@ -83,6 +96,11 @@ function logic.update_annunciator(self)
-- some alarms wait until the burn rate has stabilized, so keep track of that -- some alarms wait until the burn rate has stabilized, so keep track of that
if math.abs(_get_dt(DT_KEYS.ReactorBurnR)) > 0 then if math.abs(_get_dt(DT_KEYS.ReactorBurnR)) > 0 then
self.last_rate_change_ms = util.time_ms() self.last_rate_change_ms = util.time_ms()
self.turbine_flow_stable = false
for t = 1, self.num_turbines do
self.turbine_stability_data[t] = { time_state = 0, time_tanks = 0, rotation = 1 }
end
end end
-- record reactor stats -- record reactor stats
@@ -115,7 +133,15 @@ function logic.update_annunciator(self)
self.last_heartbeat = plc_db.last_status_update self.last_heartbeat = plc_db.last_status_update
end end
local flow_low = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, ANNUNC_LIMS.RCSFlowLow_NA, ANNUNC_LIMS.RCSFlowLow_H2O) local flow_low = ANNUNC_LIMS.RCSFlowLow_H2O
local high_temp = plc_db.max_op_temp_H2O
if plc_db.mek_status.ccool_type == types.FLUID.SODIUM then
flow_low = ANNUNC_LIMS.RCSFlowLow_NA
high_temp = plc_db.max_op_temp_Na
end
self.plc_cache.high_temp_lim = math.min(high_temp + ANNUNC_LIMS.OpTempTolerance, 1200)
-- update other annunciator fields -- update other annunciator fields
annunc.ReactorSCRAM = plc_db.rps_tripped annunc.ReactorSCRAM = plc_db.rps_tripped
@@ -124,7 +150,7 @@ function logic.update_annunciator(self)
annunc.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool) annunc.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool)
annunc.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low annunc.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low
annunc.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow annunc.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow
annunc.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh annunc.ReactorTempHigh = plc_db.mek_status.temp >= self.plc_cache.high_temp_lim
annunc.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT annunc.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT
annunc.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow annunc.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow
annunc.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh annunc.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh
@@ -143,9 +169,9 @@ function logic.update_annunciator(self)
self.plc_cache.ok = false self.plc_cache.ok = false
end end
--------------- --#endregion
-- MISC RTUs --
--------------- --#region Misc RTUs
local max_rad, any_faulted = 0, false local max_rad, any_faulted = 0, false
@@ -170,9 +196,9 @@ function logic.update_annunciator(self)
end end
end end
------------- --#endregion
-- BOILERS --
------------- --#region Boilers
local boilers_ready = num_boilers == #self.boilers local boilers_ready = num_boilers == #self.boilers
@@ -230,9 +256,9 @@ function logic.update_annunciator(self)
boiler_water_dt_sum = _get_dt(DT_KEYS.ReactorCCool) boiler_water_dt_sum = _get_dt(DT_KEYS.ReactorCCool)
end end
--------------------------- --#endregion
-- COOLANT FEED MISMATCH --
--------------------------- --#region Coolant Feed Mismatch
-- check coolant feed mismatch if using boilers, otherwise calculate with reactor -- check coolant feed mismatch if using boilers, otherwise calculate with reactor
local cfmismatch = false local cfmismatch = false
@@ -263,9 +289,9 @@ function logic.update_annunciator(self)
annunc.CoolantFeedMismatch = cfmismatch annunc.CoolantFeedMismatch = cfmismatch
-------------- --#endregion
-- TURBINES --
-------------- --#region Turbines
local turbines_ready = num_turbines == #self.turbines local turbines_ready = num_turbines == #self.turbines
@@ -276,6 +302,7 @@ function logic.update_annunciator(self)
local total_flow_rate = 0 local total_flow_rate = 0
local total_input_rate = 0 local total_input_rate = 0
local max_water_return_rate = 0 local max_water_return_rate = 0
local turbines_stable = true
-- recompute blade count on the chance that it may have changed -- recompute blade count on the chance that it may have changed
self.db.control.blade_count = 0 self.db.control.blade_count = 0
@@ -284,12 +311,14 @@ function logic.update_annunciator(self)
for i = 1, #self.turbines do for i = 1, #self.turbines do
local session = self.turbines[i] ---@type unit_session local session = self.turbines[i] ---@type unit_session
local turbine = session.get_db() ---@type turbinev_session_db local turbine = session.get_db() ---@type turbinev_session_db
local idx = session.get_device_idx()
annunc.RCSFault = annunc.RCSFault or (not turbine.formed) or session.is_faulted() annunc.RCSFault = annunc.RCSFault or (not turbine.formed) or session.is_faulted()
annunc.TurbineOnline[idx] = true
-- update ready state -- update ready state
-- - must be formed -- - must be formed
-- - must have received build, state, and tanks at least once -- - must have received build, state, and tanks at least once
turbines_ready = turbines_ready and turbine.formed and turbines_ready = turbines_ready and turbine.formed and
(turbine.build.last_update > 0) and (turbine.build.last_update > 0) and
(turbine.state.last_update > 0) and (turbine.state.last_update > 0) and
@@ -298,11 +327,56 @@ function logic.update_annunciator(self)
total_flow_rate = total_flow_rate + turbine.state.flow_rate total_flow_rate = total_flow_rate + turbine.state.flow_rate
total_input_rate = total_input_rate + turbine.state.steam_input_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate
max_water_return_rate = max_water_return_rate + turbine.build.max_water_output max_water_return_rate = max_water_return_rate + turbine.build.max_water_output
self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades
annunc.TurbineOnline[session.get_device_idx()] = true local last = self.turbine_stability_data[i]
if (not self.turbine_flow_stable) and (turbine.state.steam_input_rate > 0) then
local rotation = turbine_rotation(turbine)
local rotation_stable = false
-- see if data updated, and if so, check rotation speed change
-- minimal change indicates the turbine is converging on a flow rate
if last.time_tanks < turbine.tanks.last_update then
if last.time_tanks > 0 then
rotation_stable = math.abs(rotation - last.rotation) < 0.00000003
end
last.time_tanks = turbine.tanks.last_update
last.rotation = rotation
end
-- flow is stable if the flow rate is at the input rate or at the max (±1 mB/t)
local flow_stable = false
if last.time_state < turbine.state.last_update then
if (last.time_state > 0) and (turbine.state.flow_rate > 0) then
flow_stable = math.abs(turbine.state.flow_rate - math.min(turbine.state.steam_input_rate, turbine.build.max_flow_rate)) < 2
end
last.time_state = turbine.state.last_update
end
if rotation_stable then
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached rotational stability (", rotation, ")"))
end
if flow_stable then
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)"))
end
turbines_stable = turbines_stable and (rotation_stable or flow_stable)
else
last.time_state = 0
last.time_tanks = 0
last.rotation = 1
turbines_stable = false
end
end end
self.turbine_flow_stable = self.turbine_flow_stable or turbines_stable
-- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine -- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine
annunc.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) annunc.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate)
@@ -340,6 +414,8 @@ function logic.update_annunciator(self)
annunc.TurbineTrip[idx] = has_steam and db.state.flow_rate == 0 annunc.TurbineTrip[idx] = has_steam and db.state.flow_rate == 0
end end
--#endregion
-- update auto control ready state for this unit -- update auto control ready state for this unit
self.db.control.ready = plc_ready and boilers_ready and turbines_ready self.db.control.ready = plc_ready and boilers_ready and turbines_ready
end end
@@ -474,7 +550,8 @@ function logic.update_alarms(self)
end end
-- High Temperature -- High Temperature
_update_alarm_state(self, plc_cache.temp >= ALARM_LIMS.HIGH_TEMP, self.alarms.ReactorHighTemp) local high_temp = math.min(math.max(self.plc_cache.high_temp_lim, 1100), 1199.995)
_update_alarm_state(self, plc_cache.temp >= high_temp, self.alarms.ReactorHighTemp)
-- Waste Leak -- Waste Leak
_update_alarm_state(self, plc_cache.waste >= 1.0, self.alarms.ReactorWasteLeak) _update_alarm_state(self, plc_cache.waste >= 1.0, self.alarms.ReactorWasteLeak)
@@ -508,11 +585,25 @@ function logic.update_alarms(self)
local rcs_trans = any_low or any_over or gen_trip or annunc.RCPTrip or annunc.MaxWaterReturnFeed local rcs_trans = any_low or any_over or gen_trip or annunc.RCPTrip or annunc.MaxWaterReturnFeed
-- annunciator indicators for these states may not indicate a real issue when: if plc_cache.active then
-- > flow is ramping up right after reactor start -- these conditions may not indicate an issue when flow is changing after a burn rate change
-- > flow is ramping down after reactor shutdown if self.num_boilers == 0 then
if ((util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS) and plc_cache.active then if (util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS then
rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch rcs_trans = rcs_trans or annunc.BoilRateMismatch
end
if self.turbine_flow_stable then
rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch
end
else
if (util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS then
rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch
end
if self.turbine_flow_stable then
rcs_trans = rcs_trans or annunc.SteamFeedMismatch
end
end
end end
if _update_alarm_state(self, rcs_trans, self.alarms.RCSTransient) then if _update_alarm_state(self, rcs_trans, self.alarms.RCSTransient) then
@@ -636,11 +727,11 @@ function logic.update_status_text(self)
self.status_text[2] = "elevated level of radiation" self.status_text[2] = "elevated level of radiation"
end end
elseif is_active(self.alarms.ReactorOverTemp) then elseif is_active(self.alarms.ReactorOverTemp) then
self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" } self.status_text = { "CORE OVER TEMP", "reactor core temp damaging" }
elseif is_active(self.alarms.ReactorWasteLeak) then elseif is_active(self.alarms.ReactorWasteLeak) then
self.status_text = { "WASTE LEAK", "radioactive waste leak detected" } self.status_text = { "WASTE LEAK", "radioactive waste leak detected" }
elseif is_active(self.alarms.ReactorHighTemp) then elseif is_active(self.alarms.ReactorHighTemp) then
self.status_text = { "CORE TEMP HIGH", "reactor core temperature >1150K" } self.status_text = { "CORE TEMP HIGH", "reactor core temperature high" }
elseif is_active(self.alarms.ReactorHighWaste) then elseif is_active(self.alarms.ReactorHighWaste) then
self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" } self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" }
elseif is_active(self.alarms.TurbineTrip) then elseif is_active(self.alarms.TurbineTrip) then
@@ -666,7 +757,9 @@ function logic.update_status_text(self)
elseif annunc.WasteLineOcclusion then elseif annunc.WasteLineOcclusion then
self.status_text[2] = "insufficient waste output rate" self.status_text[2] = "insufficient waste output rate"
elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then
self.status_text[2] = "awaiting flow stability" self.status_text[2] = "awaiting coolant flow stability"
elseif not self.turbine_flow_stable then
self.status_text[2] = "awaiting turbine flow stability"
else else
self.status_text[2] = "system nominal" self.status_text[2] = "system nominal"
end end
@@ -730,6 +823,8 @@ end
function logic.handle_redstone(self) function logic.handle_redstone(self)
local AISTATE = self.types.AISTATE local AISTATE = self.types.AISTATE
local annunc = self.db.annunciator local annunc = self.db.annunciator
local cache = self.plc_cache
local rps = cache.rps_status
-- check if an alarm is active (tripped or ack'd) -- check if an alarm is active (tripped or ack'd)
---@nodiscard ---@nodiscard
@@ -741,18 +836,18 @@ function logic.handle_redstone(self)
-- reactor controls -- reactor controls
if self.plc_s ~= nil then if self.plc_s ~= nil then
if (not self.plc_cache.rps_status.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then if (not rps.manual) and self.io_ctl.digital_read(IO.R_SCRAM) then
-- reactor SCRAM requested but not yet done; perform it -- reactor SCRAM requested but not yet done; perform it
self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM)
end end
if self.plc_cache.rps_trip and self.io_ctl.digital_read(IO.R_RESET) then if cache.rps_trip and self.io_ctl.digital_read(IO.R_RESET) then
-- reactor RPS reset requested but not yet done; perform it -- reactor RPS reset requested but not yet done; perform it
self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET)
end end
if (not self.auto_engaged) and (not self.plc_cache.active) and if (not self.auto_engaged) and (not cache.active) and
(not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ENABLE) then (not cache.rps_trip) and self.io_ctl.digital_read(IO.R_ENABLE) then
-- reactor enable requested and allowable, but not yet done; perform it -- reactor enable requested and allowable, but not yet done; perform it
self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE)
end end
@@ -761,25 +856,23 @@ function logic.handle_redstone(self)
-- check for request to ack all alarms -- check for request to ack all alarms
if self.io_ctl.digital_read(IO.U_ACK) then if self.io_ctl.digital_read(IO.U_ACK) then
for i = 1, #self.db.alarm_states do for i = 1, #self.db.alarm_states do
if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then self.db.alarm_states[i] = ALARM_STATE.ACKED end
self.db.alarm_states[i] = ALARM_STATE.ACKED
end
end end
end end
-- write reactor status outputs -- write reactor status outputs
self.io_ctl.digital_write(IO.R_ACTIVE, self.plc_cache.active) self.io_ctl.digital_write(IO.R_ACTIVE, cache.active)
self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.auto_engaged) self.io_ctl.digital_write(IO.R_AUTO_CTRL, self.auto_engaged)
self.io_ctl.digital_write(IO.R_SCRAMMED, self.plc_cache.rps_trip) self.io_ctl.digital_write(IO.R_SCRAMMED, cache.rps_trip)
self.io_ctl.digital_write(IO.R_AUTO_SCRAM, self.plc_cache.rps_status.automatic) self.io_ctl.digital_write(IO.R_AUTO_SCRAM, rps.automatic)
self.io_ctl.digital_write(IO.R_HIGH_DMG, self.plc_cache.rps_status.high_dmg) self.io_ctl.digital_write(IO.R_HIGH_DMG, rps.high_dmg)
self.io_ctl.digital_write(IO.R_HIGH_TEMP, self.plc_cache.rps_status.high_temp) self.io_ctl.digital_write(IO.R_HIGH_TEMP, rps.high_temp)
self.io_ctl.digital_write(IO.R_LOW_COOLANT, self.plc_cache.rps_status.low_cool) self.io_ctl.digital_write(IO.R_LOW_COOLANT, rps.low_cool)
self.io_ctl.digital_write(IO.R_EXCESS_HC, self.plc_cache.rps_status.ex_hcool) self.io_ctl.digital_write(IO.R_EXCESS_HC, rps.ex_hcool)
self.io_ctl.digital_write(IO.R_EXCESS_WS, self.plc_cache.rps_status.ex_waste) self.io_ctl.digital_write(IO.R_EXCESS_WS, rps.ex_waste)
self.io_ctl.digital_write(IO.R_INSUFF_FUEL, self.plc_cache.rps_status.no_fuel) self.io_ctl.digital_write(IO.R_INSUFF_FUEL, rps.no_fuel)
self.io_ctl.digital_write(IO.R_PLC_FAULT, self.plc_cache.rps_status.fault) self.io_ctl.digital_write(IO.R_PLC_FAULT, rps.fault)
self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, self.plc_cache.rps_status.timeout) self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, rps.timeout)
-- write unit outputs -- write unit outputs
@@ -797,13 +890,28 @@ function logic.handle_redstone(self)
-- Emergency Coolant -- -- Emergency Coolant --
----------------------- -----------------------
local enable_emer_cool = self.plc_cache.rps_status.low_cool or local boiler_water_low = false
(self.auto_engaged and annunc.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp)) for i = 1, #annunc.WaterLevelLow do boiler_water_low = boiler_water_low or annunc.WaterLevelLow[i] end
local enable_emer_cool = rps.low_cool or
(self.auto_engaged and
(annunc.CoolantLevelLow or (boiler_water_low and rps.ex_hcool)) and
is_active(self.alarms.ReactorOverTemp))
if enable_emer_cool and not self.emcool_opened then
log.debug(util.c(">> Emergency Coolant Enable Detail Report (Unit ", self.r_id, ") <<"))
log.debug(util.c("| CoolantLevelLow[", annunc.CoolantLevelLow, "] CoolantLevelLowLow[", rps.low_cool, "] ExcessHeatedCoolant[", rps.ex_hcool, "]"))
log.debug(util.c("| ReactorOverTemp[", AISTATE_NAMES[self.alarms.ReactorOverTemp.state], "]"))
for i = 1, #annunc.WaterLevelLow do
log.debug(util.c("| WaterLevelLow(", i, ")[", annunc.WaterLevelLow[i], "]"))
end
end
-- don't turn off emergency coolant on sufficient coolant level since it might drop again -- don't turn off emergency coolant on sufficient coolant level since it might drop again
-- turn off once system is OK again -- turn off once system is OK again
-- if auto control is engaged, alarm check will SCRAM on reactor over temp so that's covered -- if auto control is engaged, alarm check will SCRAM on reactor over temp so that's covered
if not self.plc_cache.rps_trip then if not cache.rps_trip then
-- set turbines to not dump steam -- set turbines to not dump steam
for i = 1, #self.turbines do for i = 1, #self.turbines do
local session = self.turbines[i] ---@type unit_session local session = self.turbines[i] ---@type unit_session

View File

@@ -1,16 +1,28 @@
require("/initenv").init_env() require("/initenv").init_env()
local rsio = require("scada-common.rsio") local rsio = require("scada-common.rsio")
local util = require("scada-common.util") local util = require("scada-common.util")
local testutils = require("test.testutils") local testutils = require("test.testutils")
local IO = rsio.IO
local IO_LVL = rsio.IO_LVL
local IO_MODE = rsio.IO_MODE
local print = util.print local print = util.print
local println = util.println local println = util.println
local IO = rsio.IO -- list of inverted digital signals<br>
local IO_LVL = rsio.IO_LVL -- only using the key for a quick lookup, value just can't be nil
local IO_MODE = rsio.IO_MODE local DIG_INV = {
[IO.F_SCRAM] = 0,
[IO.R_SCRAM] = 0,
[IO.WASTE_PU] = 0,
[IO.WASTE_PO] = 0,
[IO.WASTE_POPL] = 0,
[IO.WASTE_AM] = 0,
[IO.U_EMER_COOL] = 0
}
println("starting RSIO tester") println("starting RSIO tester")
println("") println("")
@@ -50,8 +62,8 @@ testutils.pause()
println(">>> checking invalid ports:") println(">>> checking invalid ports:")
testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "") testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "UNKNOWN")
testutils.test_func_nil("rsio.to_string", rsio.to_string, "") testutils.test_func_nil("rsio.to_string", rsio.to_string, "UNKNOWN")
testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN) testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN)
testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN) testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN)
@@ -100,46 +112,35 @@ println(">>> checking port I/O:")
print("rsio.digital_is_active(...): ") print("rsio.digital_is_active(...): ")
-- check input ports -- check all digital ports
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.LOW) == true, "IO_F_SCRAM_HIGH") for i = 1, rsio.NUM_PORTS do
assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.HIGH) == false, "IO_F_SCRAM_LOW") if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.LOW) == true, "IO_R_SCRAM_HIGH") local high = DIG_INV[i] == nil
assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.HIGH) == false, "IO_R_SCRAM_LOW") assert(rsio.digital_is_active(i, IO_LVL.LOW) == not high, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.LOW) == false, "IO_R_ENABLE_HIGH") assert(rsio.digital_is_active(i, IO_LVL.HIGH) == high, "IO_" .. rsio.to_string(i) .. "_HIGH")
assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.HIGH) == true, "IO_R_ENABLE_LOW") end
end
-- non-inputs should always return LOW assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.LOW) == nil, "ANA_DIG_READ_LOW")
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.LOW) == false, "IO_OUT_READ_LOW") assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.HIGH) == nil, "ANA_DIG_READ_HIGH")
assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.HIGH) == false, "IO_OUT_READ_HIGH")
println("PASS") println("PASS")
-- check output ports -- check digital write
print("rsio.digital_write(...): ") print("rsio.digital_write_active(...): ")
-- check output ports -- check all digital ports
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.LOW, "IO_F_ALARM_LOW") for i = 1, rsio.NUM_PORTS do
assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_HIGH") if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.HIGH, "IO_WASTE_PU_HIGH") local high = DIG_INV[i] == nil
assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_LOW") assert(rsio.digital_write_active(i, not high) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.HIGH, "IO_WASTE_PO_HIGH") assert(rsio.digital_write_active(i, high) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH")
assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_LOW") end
assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.HIGH, "IO_WASTE_POPL_HIGH")
assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.LOW, "IO_WASTE_POPL_LOW")
assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.HIGH, "IO_WASTE_AM_HIGH")
assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.LOW, "IO_WASTE_AM_LOW")
-- check all reactor output ports (all are active high)
for i = IO.R_ALARM, (IO.R_PLC_TIMEOUT - IO.R_ALARM + 1) do
assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_PORT")
assert(rsio.digital_write_active(i, false) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW")
assert(rsio.digital_write_active(i, true) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH")
end end
-- non-outputs should always return false assert(rsio.digital_write_active(IO.F_MATRIX_CHG, true) == false, "ANA_DIG_WRITE_TRUE")
assert(rsio.digital_write_active(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_FALSE") assert(rsio.digital_write_active(IO.F_MATRIX_CHG, false) == false, "ANA_DIG_WRITE_FALSE")
assert(rsio.digital_write_active(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_TRUE")
println("PASS") println("PASS")

View File

@@ -0,0 +1,56 @@
-- add this to psil:
--[[
-- count the number of subscribers in this PSIL instance
---@return integer count
function public.count()
local c = 0
for _, val in pairs(ic) do
for _ = 1, #val.subscribers do c = c + 1 end
end
return c
end
]]--
-- add this to coordinator iocontrol front panel heartbeat function:
--[[
if io.facility then
local count = io.facility.ps.count()
count = count + io.facility.env_d_ps.count()
for x = 1, #io.facility.induction_ps_tbl do
count = count + io.facility.induction_ps_tbl[x].count()
end
for x = 1, #io.facility.sps_ps_tbl do
count = count + io.facility.sps_ps_tbl[x].count()
end
for x = 1, #io.facility.tank_ps_tbl do
count = count + io.facility.tank_ps_tbl[x].count()
end
for i = 1, #io.units do
local entry = io.units[i] ---@type ioctl_unit
count = count + entry.unit_ps.count()
for x = 1, #entry.boiler_ps_tbl do
count = count + entry.boiler_ps_tbl[x].count()
end
for x = 1, #entry.turbine_ps_tbl do
count = count + entry.turbine_ps_tbl[x].count()
end
for x = 1, #entry.tank_ps_tbl do
count = count + entry.tank_ps_tbl[x].count()
end
end
log.debug(count)
end
]]--