diff --git a/coordinator/configure.lua b/coordinator/configure.lua index 5b223e8..54fb854 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -44,7 +44,8 @@ local RIGHT = core.ALIGN.RIGHT -- changes to the config data/format to let the user know 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.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 @@ -358,7 +359,7 @@ local function config_view(display) 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} + 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 @@ -824,12 +825,27 @@ local function config_view(display) 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="By default, this project uses green/red heavily to distinguish ok and not, with some indicators also using multiple colors. By selecting a color blindness below, blues will be used instead of greens on indicators and multi-color indicators will be split up as space permits."} + 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 == 1 then + 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) @@ -838,15 +854,10 @@ local function config_view(display) end end - TextBox{parent=clr_c_2,x=1,y=8,height=1,text="Color Mode"} - local c_mode = RadioButton{parent=clr_c_2,x=1,y=9,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=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=20,y=8,height=1,text="Preview"} - local _ = IndLight{parent=clr_c_2,x=20,y=9,label="Good",colors=cpair(colors.black,colors.green)} - _ = IndLight{parent=clr_c_2,x=20,y=10,label="Warning",colors=cpair(colors.black,colors.yellow)} - _ = IndLight{parent=clr_c_2,x=20,y=11,label="Bad",colors=cpair(colors.black,colors.red)} - - TextBox{parent=clr_c_2,x=1,y=14,height=6,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg} + 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} @@ -1235,6 +1246,10 @@ local function config_view(display) function tool_ctl.gen_mon_list() 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() for iface, device in pairs(monitors) do local dev = device.dev @@ -1254,11 +1269,14 @@ local function config_view(display) if tmp_cfg.MainDisplay == iface then assignment = "Main" + missing.main = false elseif tmp_cfg.FlowDisplay == iface then assignment = "Flow" + missing.flow = false else for i = 1, tmp_cfg.UnitCount do if tmp_cfg.UnitDisplays[i] == iface then + missing.unit[i] = false assignment = "Unit " .. i break end @@ -1283,6 +1301,31 @@ local function config_view(display) if assignment == "Unused" then unset.disable() 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 -- expose the auth key on the summary page diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 85387b4..a8d6e5e 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -4,6 +4,8 @@ local ppm = require("scada-common.ppm") local util = require("scada-common.util") local types = require("scada-common.types") +local themes = require("graphics.themes") + local iocontrol = require("coordinator.iocontrol") local process = require("coordinator.process") @@ -100,7 +102,7 @@ function coordinator.load_config() 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, 4) + cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES) -- Monitor Setup diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5b3c46b..152d37b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder") local apisessions = require("coordinator.session.apisessions") -local COORDINATOR_VERSION = "v1.3.0" +local COORDINATOR_VERSION = "v1.3.5" local CHUNK_LOAD_DELAY_S = 30.0 @@ -103,6 +103,7 @@ log.info("========================================") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") crash.set_env("coordinator", COORDINATOR_VERSION) +crash.dbg_log_env() ---------------------------------------- -- main application diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index 078661a..2b80350 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -34,7 +34,8 @@ local function new_view(root, x, y, data, ps, id) local matrix = Div{parent=root,fg_bg=style.root,width=33,height=24,x=x,y=y} - local cutout_fg_bg = cpair(style.theme.bg, colors.gray) + -- black has low contrast with dark gray, so if background is black use white instead + 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} diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 455e42e..7124083 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -61,12 +61,12 @@ local function init(panel, num_units) local modem = LED{parent=system,label="MODEM",colors=led_grn} if not style.colorblind then - local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}} + 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=colors.red_off,c1=colors.red,c2=colors.green} - local nt_ver = LEDPair{parent=system,label="NT VERSION",off=colors.red_off,c1=colors.red,c2=colors.green} + 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 diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 13379b7..306fe2e 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -88,17 +88,19 @@ style.theme = smooth_stone ---@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 + 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.ind_hi_box_bg = util.trinary(colorblind, colors.black, 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(colorblind, colors.black, colors.gray) + 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(colorblind, colors.black, colors.lightGray) + style.ind_hi_box_bg = util.trinary(gray_ind_off, colors.lightGray, colors.black) end style.colorblind = colorblind diff --git a/graphics/themes.lua b/graphics/themes.lua index b59b504..7d20a60 100644 --- a/graphics/themes.lua +++ b/graphics/themes.lua @@ -54,14 +54,21 @@ themes.COLOR_MODE = { STANDARD = 1, DEUTERANOPIA = 2, PROTANOPIA = 3, - TRITANOPIA = 4 + TRITANOPIA = 4, + BLUE_IND = 5, + STD_ON_BLACK = 6, + BLUE_ON_BLACK = 7, + NUM_MODES = 8 } themes.COLOR_MODE_NAMES = { "Standard", "Deuteranopia", "Protanopia", - "Tritanopia" + "Tritanopia", + "Blue for 'Good'", + "Standard + Black", + "Blue + Black" } -- attempts to get the string name of a color mode @@ -72,7 +79,10 @@ 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 then + 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 @@ -147,6 +157,26 @@ themes.sandstone = { { 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 } } } } @@ -180,8 +210,8 @@ themes.basalt = { { c = colors.white, hex = 0xbfbfbf }, { c = colors.lightGray, hex = 0x848794 }, { c = colors.gray, hex = 0x5c5f68 }, - { c = colors.black, hex = 0x262626 }, - { c = colors.red_off, hex = 0x653839 } + { c = colors.black, hex = 0x333333 }, + { c = colors.red_off, hex = 0x512d2d } }, color_modes = { @@ -216,6 +246,26 @@ themes.basalt = { { 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 } } } } @@ -272,19 +322,33 @@ themes.smooth_stone = { { { c = colors.blue, hex = 0x1081ff }, { c = colors.yellow, hex = 0xf7c311 }, - { c = colors.red, hex = 0xfb5615 }, + { c = colors.red, hex = 0xfb5615 } }, -- protanopia { { c = colors.blue, hex = 0x1081ff }, { c = colors.yellow, hex = 0xf5e633 }, - { c = colors.red, hex = 0xff521a }, + { c = colors.red, hex = 0xff521a } }, -- tritanopia { { c = colors.blue, hex = 0x40cbd7 }, { c = colors.yellow, hex = 0xffbc00 }, - { c = colors.red, hex = 0xff0000 }, + { 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 } } } } @@ -318,19 +382,33 @@ themes.deepslate = { { { c = colors.blue, hex = 0x65aeff }, { c = colors.yellow, hex = 0xf7c311 }, - { c = colors.red, hex = 0xfb5615 }, + { c = colors.red, hex = 0xfb5615 } }, -- protanopia { { c = colors.blue, hex = 0x65aeff }, { c = colors.yellow, hex = 0xf5e633 }, - { c = colors.red, hex = 0xff8058 }, + { c = colors.red, hex = 0xff8058 } }, -- tritanopia { { c = colors.blue, hex = 0x00ecff }, { c = colors.yellow, hex = 0xffbc00 }, - { c = colors.red, hex = 0xdf4949 }, + { 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 } } } } diff --git a/pocket/startup.lua b/pocket/startup.lua index 3bff062..5b7c91f 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.7.2-alpha" +local POCKET_VERSION = "v0.7.3-alpha" local println = util.println local println_ts = util.println_ts @@ -54,6 +54,7 @@ log.info("BOOTING pocket.startup " .. POCKET_VERSION) log.info("========================================") crash.set_env("pocket", POCKET_VERSION) +crash.dbg_log_env() ---------------------------------------- -- main application diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 7f6dce5..43db0a9 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -39,7 +39,8 @@ local RIGHT = core.ALIGN.RIGHT local changes = { { "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } }, { "v1.6.8", { "ConnTimeout can now have a fractional part" } }, - { "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } } + { "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 @@ -215,7 +216,7 @@ local function config_view(display) 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} + 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 @@ -462,12 +463,27 @@ local function config_view(display) 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="By default, this project uses green/red heavily to distinguish ok and not, with some indicators also using multiple colors. By selecting a color blindness below, blues will be used instead of greens on indicators and multi-color indicators will be split up as space permits."} + 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 == 1 then + 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) @@ -476,15 +492,10 @@ local function config_view(display) end end - TextBox{parent=clr_c_2,x=1,y=8,height=1,text="Color Mode"} - local c_mode = RadioButton{parent=clr_c_2,x=1,y=9,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=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=20,y=8,height=1,text="Preview"} - local _ = IndLight{parent=clr_c_2,x=20,y=9,label="Good",colors=cpair(colors.black,colors.green)} - _ = IndLight{parent=clr_c_2,x=20,y=10,label="Warning",colors=cpair(colors.black,colors.yellow)} - _ = IndLight{parent=clr_c_2,x=20,y=11,label="Bad",colors=cpair(colors.black,colors.red)} - - TextBox{parent=clr_c_2,x=1,y=14,height=6,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg} + 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} diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index b54cd04..ee2f3ed 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -60,12 +60,12 @@ local function init(panel) local modem = LED{parent=system,label="MODEM",colors=ind_grn} if not style.colorblind then - local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}} + 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=colors.red_off,c1=colors.red,c2=colors.green} - local nt_ver = LEDPair{parent=system,label="NT VERSION",off=colors.red_off,c1=colors.red,c2=colors.green} + 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) diff --git a/reactor-plc/panel/style.lua b/reactor-plc/panel/style.lua index 08c1192..c170d67 100644 --- a/reactor-plc/panel/style.lua +++ b/reactor-plc/panel/style.lua @@ -29,7 +29,13 @@ function style.set_theme(fp, color_mode) style.fp = themes.get_fp_style(style.theme) - style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD + 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 diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 560f3ba..b5c299d 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -6,6 +6,8 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") +local themes = require("graphics.themes") + local databus = require("reactor-plc.databus") local plc = {} @@ -84,7 +86,7 @@ function plc.load_config() 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, 4) + cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES) -- check emergency coolant configuration if enabled if config.EmerCoolEnable then @@ -144,9 +146,9 @@ function plc.rps_init(reactor, is_formed) local function _check_and_handle_ppm_call(result) if result == ppm.ACCESS_FAULT then _set_fault() - elseif result == ppm.UNDEFINED_FIELD then - _set_fault() - self.formed = false + + -- 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 diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index ea51bf6..6ad612b 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.7.0" +local R_PLC_VERSION = "v1.7.4" local println = util.println local println_ts = util.println_ts @@ -55,6 +55,7 @@ log.info("========================================") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") crash.set_env("reactor-plc", R_PLC_VERSION) +crash.dbg_log_env() ---------------------------------------- -- main application @@ -144,13 +145,6 @@ local function main() println("init> fission reactor is not formed") log.warning("init> reactor logic adapter present, but reactor is not formed") - plc_state.degraded = true - plc_state.reactor_formed = false - elseif smem_dev.reactor.getStatus() == ppm.UNDEFINED_FIELD then - -- reactor formed after ppm.mount_all was called - println("init> fission reactor was not formed") - log.warning("init> reactor reported formed, but multiblock functions are not available") - plc_state.degraded = true plc_state.reactor_formed = false end @@ -185,6 +179,7 @@ local function main() local message plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) + -- ...or not if not plc_state.fp_ok then println_ts(util.c("UI error: ", message)) println("init> running without front panel") diff --git a/rtu/configure.lua b/rtu/configure.lua index 587ffeb..7758dc2 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -77,7 +77,8 @@ assert(#PORT_DSGN == rsio.NUM_PORTS) -- changes to the config data/format to let the user know local changes = { { "v1.7.9", { "ConnTimeout can now have a fractional part" } }, - { "v1.7.15", { "Added front panel UI theme", "Added color accessibility modes" } } + { "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 @@ -321,7 +322,7 @@ local function config_view(display) 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} + 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 @@ -517,12 +518,27 @@ local function config_view(display) 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="By default, this project uses green/red heavily to distinguish ok and not, with some indicators also using multiple colors. By selecting a color blindness below, blues will be used instead of greens on indicators and multi-color indicators will be split up as space permits."} + 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 == 1 then + 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) @@ -531,15 +547,10 @@ local function config_view(display) end end - TextBox{parent=clr_c_2,x=1,y=8,height=1,text="Color Mode"} - local c_mode = RadioButton{parent=clr_c_2,x=1,y=9,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=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=20,y=8,height=1,text="Preview"} - local _ = IndLight{parent=clr_c_2,x=20,y=9,label="Good",colors=cpair(colors.black,colors.green)} - _ = IndLight{parent=clr_c_2,x=20,y=10,label="Warning",colors=cpair(colors.black,colors.yellow)} - _ = IndLight{parent=clr_c_2,x=20,y=11,label="Bad",colors=cpair(colors.black,colors.red)} - - TextBox{parent=clr_c_2,x=1,y=14,height=6,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg} + 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} diff --git a/rtu/dev/boilerv_rtu.lua b/rtu/dev/boilerv_rtu.lua index cb4f701..24ffff1 100644 --- a/rtu/dev/boilerv_rtu.lua +++ b/rtu/dev/boilerv_rtu.lua @@ -9,58 +9,49 @@ local boilerv_rtu = {} function boilerv_rtu.new(boiler) local unit = rtu.init_unit(boiler) - -- disable auto fault clearing - boiler.__p_clear_fault() - boiler.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(boiler.isFormed) + unit.connect_di("isFormed") -- coils -- -- none -- input registers -- -- multiblock properties - unit.connect_input_reg(boiler.getLength) - unit.connect_input_reg(boiler.getWidth) - unit.connect_input_reg(boiler.getHeight) - unit.connect_input_reg(boiler.getMinPos) - unit.connect_input_reg(boiler.getMaxPos) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- build properties - unit.connect_input_reg(boiler.getBoilCapacity) - unit.connect_input_reg(boiler.getSteamCapacity) - unit.connect_input_reg(boiler.getWaterCapacity) - unit.connect_input_reg(boiler.getHeatedCoolantCapacity) - unit.connect_input_reg(boiler.getCooledCoolantCapacity) - unit.connect_input_reg(boiler.getSuperheaters) - unit.connect_input_reg(boiler.getMaxBoilRate) + unit.connect_input_reg("getBoilCapacity") + unit.connect_input_reg("getSteamCapacity") + unit.connect_input_reg("getWaterCapacity") + unit.connect_input_reg("getHeatedCoolantCapacity") + unit.connect_input_reg("getCooledCoolantCapacity") + unit.connect_input_reg("getSuperheaters") + unit.connect_input_reg("getMaxBoilRate") -- current state - unit.connect_input_reg(boiler.getTemperature) - unit.connect_input_reg(boiler.getBoilRate) - unit.connect_input_reg(boiler.getEnvironmentalLoss) + unit.connect_input_reg("getTemperature") + unit.connect_input_reg("getBoilRate") + unit.connect_input_reg("getEnvironmentalLoss") -- tanks - unit.connect_input_reg(boiler.getSteam) - unit.connect_input_reg(boiler.getSteamNeeded) - unit.connect_input_reg(boiler.getSteamFilledPercentage) - unit.connect_input_reg(boiler.getWater) - unit.connect_input_reg(boiler.getWaterNeeded) - unit.connect_input_reg(boiler.getWaterFilledPercentage) - unit.connect_input_reg(boiler.getHeatedCoolant) - unit.connect_input_reg(boiler.getHeatedCoolantNeeded) - unit.connect_input_reg(boiler.getHeatedCoolantFilledPercentage) - unit.connect_input_reg(boiler.getCooledCoolant) - unit.connect_input_reg(boiler.getCooledCoolantNeeded) - unit.connect_input_reg(boiler.getCooledCoolantFilledPercentage) + unit.connect_input_reg("getSteam") + unit.connect_input_reg("getSteamNeeded") + unit.connect_input_reg("getSteamFilledPercentage") + unit.connect_input_reg("getWater") + unit.connect_input_reg("getWaterNeeded") + unit.connect_input_reg("getWaterFilledPercentage") + unit.connect_input_reg("getHeatedCoolant") + unit.connect_input_reg("getHeatedCoolantNeeded") + unit.connect_input_reg("getHeatedCoolantFilledPercentage") + unit.connect_input_reg("getCooledCoolant") + unit.connect_input_reg("getCooledCoolantNeeded") + unit.connect_input_reg("getCooledCoolantFilledPercentage") -- holding registers -- -- none - -- check if any calls faulted - local faulted = boiler.__p_is_faulted() - boiler.__p_clear_fault() - boiler.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return boilerv_rtu diff --git a/rtu/dev/dynamicv_rtu.lua b/rtu/dev/dynamicv_rtu.lua index 987e366..4ef10cb 100644 --- a/rtu/dev/dynamicv_rtu.lua +++ b/rtu/dev/dynamicv_rtu.lua @@ -9,12 +9,8 @@ local dynamicv_rtu = {} function dynamicv_rtu.new(dynamic_tank) local unit = rtu.init_unit(dynamic_tank) - -- disable auto fault clearing - dynamic_tank.__p_clear_fault() - dynamic_tank.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(dynamic_tank.isFormed) + unit.connect_di("isFormed") -- coils -- unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end) @@ -22,27 +18,22 @@ function dynamicv_rtu.new(dynamic_tank) -- input registers -- -- multiblock properties - unit.connect_input_reg(dynamic_tank.getLength) - unit.connect_input_reg(dynamic_tank.getWidth) - unit.connect_input_reg(dynamic_tank.getHeight) - unit.connect_input_reg(dynamic_tank.getMinPos) - unit.connect_input_reg(dynamic_tank.getMaxPos) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- build properties - unit.connect_input_reg(dynamic_tank.getTankCapacity) - unit.connect_input_reg(dynamic_tank.getChemicalTankCapacity) + unit.connect_input_reg("getTankCapacity") + unit.connect_input_reg("getChemicalTankCapacity") -- tanks/containers - unit.connect_input_reg(dynamic_tank.getStored) - unit.connect_input_reg(dynamic_tank.getFilledPercentage) + unit.connect_input_reg("getStored") + unit.connect_input_reg("getFilledPercentage") -- holding registers -- - unit.connect_holding_reg(dynamic_tank.getContainerEditMode, dynamic_tank.setContainerEditMode) + unit.connect_holding_reg("getContainerEditMode", "setContainerEditMode") - -- check if any calls faulted - local faulted = dynamic_tank.__p_is_faulted() - dynamic_tank.__p_clear_fault() - dynamic_tank.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return dynamicv_rtu diff --git a/rtu/dev/imatrix_rtu.lua b/rtu/dev/imatrix_rtu.lua index f3b7f5c..61de630 100644 --- a/rtu/dev/imatrix_rtu.lua +++ b/rtu/dev/imatrix_rtu.lua @@ -9,45 +9,36 @@ local imatrix_rtu = {} function imatrix_rtu.new(imatrix) local unit = rtu.init_unit(imatrix) - -- disable auto fault clearing - imatrix.__p_clear_fault() - imatrix.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(imatrix.isFormed) + unit.connect_di("isFormed") -- coils -- -- none -- input registers -- -- multiblock properties - unit.connect_input_reg(imatrix.getLength) - unit.connect_input_reg(imatrix.getWidth) - unit.connect_input_reg(imatrix.getHeight) - unit.connect_input_reg(imatrix.getMinPos) - unit.connect_input_reg(imatrix.getMaxPos) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- build properties - unit.connect_input_reg(imatrix.getMaxEnergy) - unit.connect_input_reg(imatrix.getTransferCap) - unit.connect_input_reg(imatrix.getInstalledCells) - unit.connect_input_reg(imatrix.getInstalledProviders) + unit.connect_input_reg("getMaxEnergy") + unit.connect_input_reg("getTransferCap") + unit.connect_input_reg("getInstalledCells") + unit.connect_input_reg("getInstalledProviders") -- I/O rates - unit.connect_input_reg(imatrix.getLastInput) - unit.connect_input_reg(imatrix.getLastOutput) + unit.connect_input_reg("getLastInput") + unit.connect_input_reg("getLastOutput") -- tanks - unit.connect_input_reg(imatrix.getEnergy) - unit.connect_input_reg(imatrix.getEnergyNeeded) - unit.connect_input_reg(imatrix.getEnergyFilledPercentage) + unit.connect_input_reg("getEnergy") + unit.connect_input_reg("getEnergyNeeded") + unit.connect_input_reg("getEnergyFilledPercentage") -- holding registers -- -- none - -- check if any calls faulted - local faulted = imatrix.__p_is_faulted() - imatrix.__p_clear_fault() - imatrix.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return imatrix_rtu diff --git a/rtu/dev/sps_rtu.lua b/rtu/dev/sps_rtu.lua index 448dc48..14a6e07 100644 --- a/rtu/dev/sps_rtu.lua +++ b/rtu/dev/sps_rtu.lua @@ -9,50 +9,41 @@ local sps_rtu = {} function sps_rtu.new(sps) local unit = rtu.init_unit(sps) - -- disable auto fault clearing - sps.__p_clear_fault() - sps.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(sps.isFormed) + unit.connect_di("isFormed") -- coils -- -- none -- input registers -- -- multiblock properties - unit.connect_input_reg(sps.getLength) - unit.connect_input_reg(sps.getWidth) - unit.connect_input_reg(sps.getHeight) - unit.connect_input_reg(sps.getMinPos) - unit.connect_input_reg(sps.getMaxPos) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- build properties - unit.connect_input_reg(sps.getCoils) - unit.connect_input_reg(sps.getInputCapacity) - unit.connect_input_reg(sps.getOutputCapacity) - unit.connect_input_reg(sps.getMaxEnergy) + unit.connect_input_reg("getCoils") + unit.connect_input_reg("getInputCapacity") + unit.connect_input_reg("getOutputCapacity") + unit.connect_input_reg("getMaxEnergy") -- current state - unit.connect_input_reg(sps.getProcessRate) + unit.connect_input_reg("getProcessRate") -- tanks - unit.connect_input_reg(sps.getInput) - unit.connect_input_reg(sps.getInputNeeded) - unit.connect_input_reg(sps.getInputFilledPercentage) - unit.connect_input_reg(sps.getOutput) - unit.connect_input_reg(sps.getOutputNeeded) - unit.connect_input_reg(sps.getOutputFilledPercentage) - unit.connect_input_reg(sps.getEnergy) - unit.connect_input_reg(sps.getEnergyNeeded) - unit.connect_input_reg(sps.getEnergyFilledPercentage) + unit.connect_input_reg("getInput") + unit.connect_input_reg("getInputNeeded") + unit.connect_input_reg("getInputFilledPercentage") + unit.connect_input_reg("getOutput") + unit.connect_input_reg("getOutputNeeded") + unit.connect_input_reg("getOutputFilledPercentage") + unit.connect_input_reg("getEnergy") + unit.connect_input_reg("getEnergyNeeded") + unit.connect_input_reg("getEnergyFilledPercentage") -- holding registers -- -- none - -- check if any calls faulted - local faulted = sps.__p_is_faulted() - sps.__p_clear_fault() - sps.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return sps_rtu diff --git a/rtu/dev/turbinev_rtu.lua b/rtu/dev/turbinev_rtu.lua index 4bfad81..5770ff2 100644 --- a/rtu/dev/turbinev_rtu.lua +++ b/rtu/dev/turbinev_rtu.lua @@ -9,12 +9,8 @@ local turbinev_rtu = {} function turbinev_rtu.new(turbine) local unit = rtu.init_unit(turbine) - -- disable auto fault clearing - turbine.__p_clear_fault() - turbine.__p_disable_afc() - -- discrete inputs -- - unit.connect_di(turbine.isFormed) + unit.connect_di("isFormed") -- coils -- unit.connect_coil(function () turbine.incrementDumpingMode() end, function () end) @@ -22,44 +18,39 @@ function turbinev_rtu.new(turbine) -- input registers -- -- multiblock properties - unit.connect_input_reg(turbine.getLength) - unit.connect_input_reg(turbine.getWidth) - unit.connect_input_reg(turbine.getHeight) - unit.connect_input_reg(turbine.getMinPos) - unit.connect_input_reg(turbine.getMaxPos) + unit.connect_input_reg("getLength") + unit.connect_input_reg("getWidth") + unit.connect_input_reg("getHeight") + unit.connect_input_reg("getMinPos") + unit.connect_input_reg("getMaxPos") -- build properties - unit.connect_input_reg(turbine.getBlades) - unit.connect_input_reg(turbine.getCoils) - unit.connect_input_reg(turbine.getVents) - unit.connect_input_reg(turbine.getDispersers) - unit.connect_input_reg(turbine.getCondensers) - unit.connect_input_reg(turbine.getSteamCapacity) - unit.connect_input_reg(turbine.getMaxEnergy) - unit.connect_input_reg(turbine.getMaxFlowRate) - unit.connect_input_reg(turbine.getMaxProduction) - unit.connect_input_reg(turbine.getMaxWaterOutput) + unit.connect_input_reg("getBlades") + unit.connect_input_reg("getCoils") + unit.connect_input_reg("getVents") + unit.connect_input_reg("getDispersers") + unit.connect_input_reg("getCondensers") + unit.connect_input_reg("getSteamCapacity") + unit.connect_input_reg("getMaxEnergy") + unit.connect_input_reg("getMaxFlowRate") + unit.connect_input_reg("getMaxProduction") + unit.connect_input_reg("getMaxWaterOutput") -- current state - unit.connect_input_reg(turbine.getFlowRate) - unit.connect_input_reg(turbine.getProductionRate) - unit.connect_input_reg(turbine.getLastSteamInputRate) - unit.connect_input_reg(turbine.getDumpingMode) + unit.connect_input_reg("getFlowRate") + unit.connect_input_reg("getProductionRate") + unit.connect_input_reg("getLastSteamInputRate") + unit.connect_input_reg("getDumpingMode") -- tanks/containers - unit.connect_input_reg(turbine.getSteam) - unit.connect_input_reg(turbine.getSteamNeeded) - unit.connect_input_reg(turbine.getSteamFilledPercentage) - unit.connect_input_reg(turbine.getEnergy) - unit.connect_input_reg(turbine.getEnergyNeeded) - unit.connect_input_reg(turbine.getEnergyFilledPercentage) + unit.connect_input_reg("getSteam") + unit.connect_input_reg("getSteamNeeded") + unit.connect_input_reg("getSteamFilledPercentage") + unit.connect_input_reg("getEnergy") + unit.connect_input_reg("getEnergyNeeded") + unit.connect_input_reg("getEnergyFilledPercentage") -- holding registers -- - unit.connect_holding_reg(turbine.getDumpingMode, turbine.setDumpingMode) + unit.connect_holding_reg("getDumpingMode", "setDumpingMode") - -- check if any calls faulted - local faulted = turbine.__p_is_faulted() - turbine.__p_clear_fault() - turbine.__p_enable_afc() - - return unit.interface(), faulted + return unit.interface(), false end return turbinev_rtu diff --git a/rtu/panel/front_panel.lua b/rtu/panel/front_panel.lua index d479302..738ccca 100644 --- a/rtu/panel/front_panel.lua +++ b/rtu/panel/front_panel.lua @@ -53,12 +53,12 @@ local function init(panel, units) local modem = LED{parent=system,label="MODEM",colors=ind_grn} if not style.colorblind then - local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}} + 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=colors.red_off,c1=colors.red,c2=colors.green} - local nt_ver = LEDPair{parent=system,label="NT VERSION",off=colors.red_off,c1=colors.red,c2=colors.green} + 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 diff --git a/rtu/panel/style.lua b/rtu/panel/style.lua index a72df2a..89dc302 100644 --- a/rtu/panel/style.lua +++ b/rtu/panel/style.lua @@ -28,7 +28,13 @@ function style.set_theme(fp, color_mode) style.fp = themes.get_fp_style(style.theme) - style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD + 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 diff --git a/rtu/rtu.lua b/rtu/rtu.lua index ab13bbd..7e00b98 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -5,6 +5,8 @@ local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") +local themes = require("graphics.themes") + local databus = require("rtu.databus") local modbus = require("rtu.modbus") @@ -69,7 +71,7 @@ function rtu.load_config() 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, 4) + cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES) cfv.assert_type_table(config.Peripherals) cfv.assert_type_table(config.Redstone) @@ -92,6 +94,8 @@ function rtu.init_unit(device) local insert = table.insert + local stub = function () log.warning("tried to call an RTU function stub") end + ---@class rtu_device local public = {} @@ -113,13 +117,26 @@ function rtu.init_unit(device) return self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3], self.io_count_cache[4] 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 -- connect discrete input - ---@param f function + ---@param f function|string function or function name ---@return integer count count of discrete inputs function protected.connect_di(f) - insert(self.discrete_inputs, { read = f }) + insert(self.discrete_inputs, { read = _as_func(f) }) _count_io() return #self.discrete_inputs end @@ -135,11 +152,11 @@ function rtu.init_unit(device) -- coils: single bit read-write -- connect coil - ---@param f_read function - ---@param f_write function + ---@param f_read function|string function or function name + ---@param f_write function|string function or function name ---@return integer count count of coils 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() return #self.coils end @@ -164,10 +181,10 @@ function rtu.init_unit(device) -- input registers: multi-bit read-only -- connect input register - ---@param f function + ---@param f function|string function or function name ---@return integer count count of input registers function protected.connect_input_reg(f) - insert(self.input_regs, { read = f }) + insert(self.input_regs, { read = _as_func(f) }) _count_io() return #self.input_regs end @@ -183,11 +200,11 @@ function rtu.init_unit(device) -- holding registers: multi-bit read-write -- connect holding register - ---@param f_read function - ---@param f_write function + ---@param f_read function|string function or function name + ---@param f_write function|string function or function name ---@return integer count count of holding registers 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() return #self.holding_regs end diff --git a/rtu/startup.lua b/rtu/startup.lua index 4b92756..4d7ecbd 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v1.8.0" +local RTU_VERSION = "v1.9.3" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE @@ -71,6 +71,7 @@ log.info("========================================") println(">> RTU GATEWAY " .. RTU_VERSION .. " <<") crash.set_env("rtu", RTU_VERSION) +crash.dbg_log_env() ---------------------------------------- -- main application @@ -342,7 +343,7 @@ local function main() is_multiblock = true 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")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock")) return false @@ -357,7 +358,7 @@ local function main() is_multiblock = true 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")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock")) return false @@ -377,7 +378,7 @@ local function main() is_multiblock = true 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")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock")) return false @@ -391,7 +392,7 @@ local function main() is_multiblock = true 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")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock")) return false @@ -405,7 +406,7 @@ local function main() is_multiblock = true 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")) log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock")) return false @@ -471,7 +472,8 @@ local function main() for_message = util.c("reactor ", for_reactor) 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 diff --git a/rtu/threads.lua b/rtu/threads.lua index f3147e7..011b617 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -517,82 +517,23 @@ function threads.thread__unit_comms(smem, unit) -- check if multiblock is still formed if this is a multiblock 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() + local is_formed = unit.device.isFormed() + if unit.formed == nil then unit.formed = is_formed 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 - if not unit.formed then unit.hw_state = UNIT_HW_STATE.UNFORMED end - - if (not unit.formed) and is_formed then - -- newly re-formed - local iface = ppm.get_iface(unit.device) - if iface then - 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 + if (is_formed == true) and not unit.formed then + unit.hw_state = UNIT_HW_STATE.OK + log.info(util.c(detail_name, " is now formed")) + rtu_comms.send_remounted(unit.uid) + elseif (is_formed == false) and unit.formed then + log.warning(util.c(detail_name, " is no longer formed")) end unit.formed = is_formed diff --git a/scada-common/crash.lua b/scada-common/crash.lua index 1942a8d..a7e2c93 100644 --- a/scada-common/crash.lua +++ b/scada-common/crash.lua @@ -24,6 +24,21 @@ function crash.set_env(application, version) ver = version 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 ---@param error string error message function crash.handler(error) @@ -31,13 +46,7 @@ function crash.handler(error) log.info("=====> FATAL SOFTWARE FAULT <=====") log.fatal(error) log.info("----------------------------------") - log.info(util.c("RUNTIME: ", _HOST)) - 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_versions(log.info) log.info("----------------------------------") log.info(debug.traceback("--- begin debug trace ---", 1)) log.info("--- end debug trace ---") diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 0d02663..34a6a60 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -51,11 +51,13 @@ local function peri_init(iface) self.device = peripheral.wrap(iface) end - -- initialization process (re-map) - - for key, func in pairs(self.device) do - self.fault_counts[key] = 0 - self.device[key] = function (...) + -- create a protected version of a peripheral function call + ---@nodiscard + ---@param key string function name + ---@param func function 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 status = return_table[1] @@ -85,20 +87,24 @@ local function peri_init(iface) count_str = " [" .. self.fault_counts[key] .. " total faults]" end - log.error(util.c("PPM: protected ", key, "() -> ", result, count_str)) + log.error(util.c("PPM: [@", iface, "] protected ", key, "() -> ", result, count_str)) end self.fault_counts[key] = self.fault_counts[key] + 1 - if result == "Terminated" then - ppm_sys.terminate = true - end + if result == "Terminated" then ppm_sys.terminate = true end - return ACCESS_FAULT + return ACCESS_FAULT, result 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 local function clear_fault() self.faulted = false end @@ -131,12 +137,21 @@ local function peri_init(iface) local mt = { __index = function (_, key) + -- try to find the function in case it was added (multiblock formed) + local funcs = peripheral.wrap(iface) + if (type(funcs) == "table") and (type(funcs[key]) == "function") then + -- add this function then return it + 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 + + -- function still missing, return an undefined function handler + -- note: code should avoid storing functions for multiblocks and instead try to index them again return (function () -- this will continuously be counting calls here as faults - -- unlike other functions, faults here can't be cleared as it is just not defined - if self.fault_counts[key] == nil then - self.fault_counts[key] = 0 - end + if self.fault_counts[key] == nil then self.fault_counts[key] = 0 end -- function failed self.faulted = true @@ -151,12 +166,12 @@ local function peri_init(iface) count_str = " [" .. self.fault_counts[key] .. " total calls]" end - log.error(util.c("PPM: caught undefined function ", key, "()", count_str)) + log.error(util.c("PPM: [@", iface, "] caught undefined function ", key, "()", count_str)) end self.fault_counts[key] = self.fault_counts[key] + 1 - return UNDEFINED_FIELD + return ACCESS_FAULT, UNDEFINED_FIELD end) end } diff --git a/scada-common/util.lua b/scada-common/util.lua index ebca4a2..395be22 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.1.19" +util.version = "1.2.0" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/configure.lua b/supervisor/configure.lua index f9bc10d..cd790e7 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -36,7 +36,8 @@ local RIGHT = core.ALIGN.RIGHT -- changes to the config data/format to let the user know local changes = { - { "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } } + { "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 @@ -205,7 +206,7 @@ local function config_view(display) 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} + 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 @@ -761,12 +762,27 @@ local function config_view(display) 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="By default, this project uses green/red heavily to distinguish ok and not, with some indicators also using multiple colors. By selecting a color blindness below, blues will be used instead of greens on indicators and multi-color indicators will be split up as space permits."} + 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 == 1 then + 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) @@ -775,15 +791,10 @@ local function config_view(display) end end - TextBox{parent=clr_c_2,x=1,y=8,height=1,text="Color Mode"} - local c_mode = RadioButton{parent=clr_c_2,x=1,y=9,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=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=20,y=8,height=1,text="Preview"} - local _ = IndLight{parent=clr_c_2,x=20,y=9,label="Good",colors=cpair(colors.black,colors.green)} - _ = IndLight{parent=clr_c_2,x=20,y=10,label="Warning",colors=cpair(colors.black,colors.yellow)} - _ = IndLight{parent=clr_c_2,x=20,y=11,label="Bad",colors=cpair(colors.black,colors.red)} - - TextBox{parent=clr_c_2,x=1,y=14,height=6,text="Note: exact color varies by theme.",fg_bg=g_lg_fg_bg} + 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} diff --git a/supervisor/panel/style.lua b/supervisor/panel/style.lua index a0a8bb0..f8c9311 100644 --- a/supervisor/panel/style.lua +++ b/supervisor/panel/style.lua @@ -28,7 +28,7 @@ function style.set_theme(fp, color_mode) style.fp = themes.get_fp_style(style.theme) - style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD + style.colorblind = color_mode ~= themes.COLOR_MODE.STANDARD and color_mode ~= themes.COLOR_MODE.STD_ON_BLACK end return style diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4eae495..806f7b0 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.0" +local SUPERVISOR_VERSION = "v1.3.4" local println = util.println local println_ts = util.println_ts @@ -86,6 +86,7 @@ log.info("========================================") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") crash.set_env("supervisor", SUPERVISOR_VERSION) +crash.dbg_log_env() ---------------------------------------- -- main application diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 43536c6..3d6d7c7 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -2,6 +2,8 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local util = require("scada-common.util") +local themes = require("graphics.themes") + local svsessions = require("supervisor.session.svsessions") local supervisor = {} @@ -87,7 +89,7 @@ function supervisor.load_config() 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, 4) + cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES) return cfv.valid() end diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index f43ff03..134652c 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -730,6 +730,8 @@ end function logic.handle_redstone(self) local AISTATE = self.types.AISTATE 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) ---@nodiscard @@ -741,18 +743,18 @@ function logic.handle_redstone(self) -- reactor controls 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 self.plc_s.in_queue.push_command(PLC_S_CMDS.SCRAM) 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 self.plc_s.in_queue.push_command(PLC_S_CMDS.RPS_RESET) end - if (not self.auto_engaged) and (not self.plc_cache.active) and - (not self.plc_cache.rps_trip) and self.io_ctl.digital_read(IO.R_ENABLE) then + if (not self.auto_engaged) and (not cache.active) and + (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 self.plc_s.in_queue.push_command(PLC_S_CMDS.ENABLE) end @@ -761,25 +763,23 @@ function logic.handle_redstone(self) -- check for request to ack all alarms if self.io_ctl.digital_read(IO.U_ACK) then for i = 1, #self.db.alarm_states do - if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then - self.db.alarm_states[i] = ALARM_STATE.ACKED - end + if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then self.db.alarm_states[i] = ALARM_STATE.ACKED end end end -- 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_SCRAMMED, self.plc_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_HIGH_DMG, self.plc_cache.rps_status.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_LOW_COOLANT, self.plc_cache.rps_status.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_WS, self.plc_cache.rps_status.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_PLC_FAULT, self.plc_cache.rps_status.fault) - self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, self.plc_cache.rps_status.timeout) + self.io_ctl.digital_write(IO.R_SCRAMMED, cache.rps_trip) + self.io_ctl.digital_write(IO.R_AUTO_SCRAM, rps.automatic) + self.io_ctl.digital_write(IO.R_HIGH_DMG, rps.high_dmg) + self.io_ctl.digital_write(IO.R_HIGH_TEMP, rps.high_temp) + self.io_ctl.digital_write(IO.R_LOW_COOLANT, rps.low_cool) + self.io_ctl.digital_write(IO.R_EXCESS_HC, rps.ex_hcool) + self.io_ctl.digital_write(IO.R_EXCESS_WS, rps.ex_waste) + self.io_ctl.digital_write(IO.R_INSUFF_FUEL, rps.no_fuel) + self.io_ctl.digital_write(IO.R_PLC_FAULT, rps.fault) + self.io_ctl.digital_write(IO.R_PLC_TIMEOUT, rps.timeout) -- write unit outputs @@ -797,13 +797,28 @@ function logic.handle_redstone(self) -- Emergency Coolant -- ----------------------- - local enable_emer_cool = self.plc_cache.rps_status.low_cool or - (self.auto_engaged and annunc.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp)) + local boiler_water_low = false + 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 -- 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 not self.plc_cache.rps_trip then + if not cache.rps_trip then -- set turbines to not dump steam for i = 1, #self.turbines do local session = self.turbines[i] ---@type unit_session