mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#183 RTU front panel
This commit is contained in:
parent
df45f6c984
commit
d143015cc7
@ -20,7 +20,7 @@ local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v0.13.3"
|
||||
local COORDINATOR_VERSION = "v0.13.4"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
@ -451,19 +451,13 @@ function element.new(args)
|
||||
function public.show()
|
||||
protected.window.setVisible(true)
|
||||
protected.start_anim()
|
||||
|
||||
for i = 1, #self.children do
|
||||
self.children[i].show()
|
||||
end
|
||||
for _, child in pairs(self.children) do child.show() end
|
||||
end
|
||||
|
||||
-- hide the element
|
||||
function public.hide()
|
||||
protected.stop_anim()
|
||||
for i = 1, #self.children do
|
||||
self.children[i].hide()
|
||||
end
|
||||
|
||||
for _, child in pairs(self.children) do child.hide() end
|
||||
protected.window.setVisible(false)
|
||||
end
|
||||
|
||||
|
@ -33,7 +33,7 @@ local function indicator_led(args)
|
||||
args.height = 1
|
||||
|
||||
-- determine width
|
||||
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
|
||||
args.width = math.max(args.min_label_width or 0, string.len(args.label)) + 2
|
||||
|
||||
-- flasher state
|
||||
local flash_on = true
|
||||
@ -89,8 +89,10 @@ local function indicator_led(args)
|
||||
|
||||
-- write label and initial indicator light
|
||||
e.on_update(false)
|
||||
if string.len(args.label) > 0 then
|
||||
e.window.setCursorPos(3, 1)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
end
|
||||
|
@ -37,7 +37,7 @@ local function indicator_led_pair(args)
|
||||
args.height = 1
|
||||
|
||||
-- determine width
|
||||
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
|
||||
args.width = math.max(args.min_label_width or 0, string.len(args.label)) + 2
|
||||
|
||||
-- flasher state
|
||||
local flash_on = true
|
||||
@ -103,8 +103,10 @@ local function indicator_led_pair(args)
|
||||
|
||||
-- write label and initial indicator light
|
||||
e.on_update(1)
|
||||
if string.len(args.label) > 0 then
|
||||
e.window.setCursorPos(3, 1)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
end
|
||||
|
@ -24,7 +24,7 @@ local function indicator_led_rgb(args)
|
||||
args.height = 1
|
||||
|
||||
-- determine width
|
||||
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
|
||||
args.width = math.max(args.min_label_width or 0, string.len(args.label)) + 2
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
@ -48,8 +48,10 @@ local function indicator_led_rgb(args)
|
||||
|
||||
-- write label and initial indicator light
|
||||
e.on_update(1)
|
||||
if string.len(args.label) > 0 then
|
||||
e.window.setCursorPos(3, 1)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
end
|
||||
|
4
imgen.py
4
imgen.py
@ -70,7 +70,7 @@ def make_manifest(size):
|
||||
},
|
||||
"depends" : {
|
||||
"reactor-plc" : [ "system", "common", "graphics" ],
|
||||
"rtu" : [ "system", "common" ],
|
||||
"rtu" : [ "system", "common", "graphics" ],
|
||||
"supervisor" : [ "system", "common" ],
|
||||
"coordinator" : [ "system", "common", "graphics" ],
|
||||
"pocket" : [ "system", "common", "graphics" ]
|
||||
@ -108,7 +108,7 @@ f = open("install_manifest.json", "w")
|
||||
json.dump(final_manifest, f)
|
||||
f.close()
|
||||
|
||||
if sys.argv[1] == "shields":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "shields":
|
||||
# write all the JSON files for shields.io
|
||||
for key, version in final_manifest["versions"].items():
|
||||
f = open("./shields/" + key + ".json", "w")
|
||||
|
File diff suppressed because one or more lines are too long
@ -17,7 +17,7 @@ local coreio = require("pocket.coreio")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
|
||||
local POCKET_VERSION = "alpha-v0.2.2"
|
||||
local POCKET_VERSION = "alpha-v0.2.3"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
@ -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.1.10"
|
||||
local R_PLC_VERSION = "v1.1.11"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
75
rtu/databus.lua
Normal file
75
rtu/databus.lua
Normal file
@ -0,0 +1,75 @@
|
||||
--
|
||||
-- Data Bus - Central Communication Linking for RTU Front Panel
|
||||
--
|
||||
|
||||
local psil = require("scada-common.psil")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = {}
|
||||
|
||||
local dbus_iface = {
|
||||
ps = psil.create()
|
||||
}
|
||||
|
||||
---@enum RTU_UNIT_HW_STATE
|
||||
local RTU_UNIT_HW_STATE = {
|
||||
OFFLINE = 1,
|
||||
FAULTED = 2,
|
||||
UNFORMED = 3,
|
||||
OK = 4
|
||||
}
|
||||
|
||||
databus.RTU_UNIT_HW_STATE = RTU_UNIT_HW_STATE
|
||||
|
||||
-- call to toggle heartbeat signal
|
||||
function databus.heartbeat() dbus_iface.ps.toggle("heartbeat") end
|
||||
|
||||
-- transmit firmware versions across the bus
|
||||
---@param rtu_v string RTU version
|
||||
---@param comms_v string comms version
|
||||
function databus.tx_versions(rtu_v, comms_v)
|
||||
dbus_iface.ps.publish("version", rtu_v)
|
||||
dbus_iface.ps.publish("comms_version", comms_v)
|
||||
end
|
||||
|
||||
-- transmit hardware status for modem connection state
|
||||
---@param has_modem boolean
|
||||
function databus.tx_hw_modem(has_modem)
|
||||
dbus_iface.ps.publish("has_modem", has_modem)
|
||||
end
|
||||
|
||||
-- transmit unit hardware type across the bus
|
||||
---@param uid integer unit ID
|
||||
---@param type RTU_UNIT_TYPE
|
||||
function databus.tx_unit_hw_type(uid, type)
|
||||
dbus_iface.ps.publish("unit_type_" .. uid, type)
|
||||
end
|
||||
|
||||
-- transmit unit hardware status across the bus
|
||||
---@param uid integer unit ID
|
||||
---@param status RTU_UNIT_HW_STATE
|
||||
function databus.tx_unit_hw_status(uid, status)
|
||||
dbus_iface.ps.publish("unit_hw_" .. uid, status)
|
||||
end
|
||||
|
||||
-- transmit thread (routine) statuses
|
||||
---@param thread string thread name
|
||||
---@param ok boolean thread state
|
||||
function databus.tx_rt_status(thread, ok)
|
||||
dbus_iface.ps.publish(util.c("routine__", thread), ok)
|
||||
end
|
||||
|
||||
-- transmit supervisor link state across the bus
|
||||
---@param state integer
|
||||
function databus.tx_link_state(state)
|
||||
dbus_iface.ps.publish("link_state", state)
|
||||
end
|
||||
|
||||
-- link a function to receive data from the bus
|
||||
---@param field string field name
|
||||
---@param func function function to link
|
||||
function databus.rx_field(field, func)
|
||||
dbus_iface.ps.subscribe(field, func)
|
||||
end
|
||||
|
||||
return databus
|
123
rtu/panel/front_panel.lua
Normal file
123
rtu/panel/front_panel.lua
Normal file
@ -0,0 +1,123 @@
|
||||
--
|
||||
-- Main SCADA Coordinator GUI
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("rtu.databus")
|
||||
|
||||
local style = require("rtu.panel.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local LED = require("graphics.elements.indicators.led")
|
||||
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||
|
||||
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
|
||||
|
||||
local cpair = core.graphics.cpair
|
||||
|
||||
local UNIT_TYPE_LABELS = {
|
||||
"UNKNOWN",
|
||||
"REDSTONE",
|
||||
"BOILER",
|
||||
"TURBINE",
|
||||
"IND MATRIX",
|
||||
"SPS",
|
||||
"SNA",
|
||||
"ENV DETECTOR"
|
||||
}
|
||||
|
||||
|
||||
-- create new main view
|
||||
---@param panel table main displaybox
|
||||
---@param units table unit list
|
||||
local function init(panel, units)
|
||||
TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
|
||||
--
|
||||
-- system indicators
|
||||
--
|
||||
|
||||
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
||||
|
||||
local on = LED{parent=system,label="POWER",colors=cpair(colors.green,colors.red)}
|
||||
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
|
||||
on.update(true)
|
||||
system.line_break()
|
||||
|
||||
databus.rx_field("heartbeat", heartbeat.update)
|
||||
|
||||
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||
network.update(5)
|
||||
system.line_break()
|
||||
|
||||
databus.rx_field("has_modem", modem.update)
|
||||
databus.rx_field("link_state", network.update)
|
||||
|
||||
local rt_main = LED{parent=system,label="RT MAIN",colors=cpair(colors.green,colors.green_off)}
|
||||
local rt_comm = LED{parent=system,label="RT COMMS",colors=cpair(colors.green,colors.green_off)}
|
||||
system.line_break()
|
||||
|
||||
databus.rx_field("routine__main", rt_main.update)
|
||||
databus.rx_field("routine__comms", rt_comm.update)
|
||||
|
||||
--
|
||||
-- about label
|
||||
--
|
||||
|
||||
local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
|
||||
|
||||
databus.rx_field("version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||
databus.rx_field("comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||
|
||||
--
|
||||
-- unit status list
|
||||
--
|
||||
|
||||
local threads = Div{parent=panel,width=8,height=18,x=17,y=3}
|
||||
|
||||
-- display up to 16 units
|
||||
local list_length = math.min(#units, 16)
|
||||
|
||||
-- show routine statuses
|
||||
for i = 1, list_length do
|
||||
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i),height=1}
|
||||
local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=cpair(colors.green,colors.green_off)}
|
||||
databus.rx_field("routine__unit_" .. i, rt_unit.update)
|
||||
end
|
||||
|
||||
local unit_hw_statuses = Div{parent=panel,height=18,x=25,y=3}
|
||||
|
||||
-- show hardware statuses
|
||||
for i = 1, list_length do
|
||||
local unit = units[i] ---@type rtu_unit_registry_entry
|
||||
|
||||
-- hardware status
|
||||
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}}
|
||||
|
||||
databus.rx_field("unit_hw_" .. i, unit_hw.update)
|
||||
|
||||
-- unit name identifier (type + index)
|
||||
local name = util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", unit.index)
|
||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=name,height=1}
|
||||
|
||||
databus.rx_field("unit_type_" .. i, function (t)
|
||||
name_box.set_value(util.c(UNIT_TYPE_LABELS[t + 1], " ", unit.index))
|
||||
end)
|
||||
|
||||
-- assignment (unit # or facility)
|
||||
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=cpair(colors.lightGray,colors.ivory)}
|
||||
end
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
return init
|
41
rtu/panel/style.lua
Normal file
41
rtu/panel/style.lua
Normal file
@ -0,0 +1,41 @@
|
||||
--
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local style = {}
|
||||
|
||||
local cpair = core.graphics.cpair
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
-- remap global colors
|
||||
colors.ivory = colors.pink
|
||||
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 },
|
||||
{ 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
|
||||
}
|
||||
|
||||
return style
|
81
rtu/renderer.lua
Normal file
81
rtu/renderer.lua
Normal file
@ -0,0 +1,81 @@
|
||||
--
|
||||
-- Graphics Rendering Control
|
||||
--
|
||||
|
||||
local panel_view = require("rtu.panel.front_panel")
|
||||
local style = require("rtu.panel.style")
|
||||
|
||||
local flasher = require("graphics.flasher")
|
||||
|
||||
local DisplayBox = require("graphics.elements.displaybox")
|
||||
|
||||
local renderer = {}
|
||||
|
||||
local ui = {
|
||||
display = nil
|
||||
}
|
||||
|
||||
-- start the UI
|
||||
---@param units table RTU units
|
||||
function renderer.start_ui(units)
|
||||
if ui.display == nil then
|
||||
-- reset terminal
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
-- set overridden colors
|
||||
for i = 1, #style.colors do
|
||||
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
|
||||
end
|
||||
|
||||
-- start flasher callback task
|
||||
flasher.run()
|
||||
|
||||
-- init front panel view
|
||||
ui.display = DisplayBox{window=term.current(),fg_bg=style.root}
|
||||
panel_view(ui.display, units)
|
||||
end
|
||||
end
|
||||
|
||||
-- close out the UI
|
||||
function renderer.close_ui()
|
||||
-- stop blinking indicators
|
||||
flasher.clear()
|
||||
|
||||
if ui.display ~= nil then
|
||||
-- hide to stop animation callbacks
|
||||
ui.display.hide()
|
||||
end
|
||||
|
||||
-- clear root UI elements
|
||||
ui.display = nil
|
||||
|
||||
-- restore colors
|
||||
for i = 1, #style.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.colors[i].c)
|
||||
term.setPaletteColor(style.colors[i].c, r, g, b)
|
||||
end
|
||||
|
||||
-- reset terminal
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
end
|
||||
|
||||
-- is the UI ready?
|
||||
---@nodiscard
|
||||
---@return boolean ready
|
||||
function renderer.ui_ready() return ui.display ~= nil end
|
||||
|
||||
-- handle a mouse event
|
||||
---@param event mouse_interaction
|
||||
function renderer.handle_mouse(event)
|
||||
if ui.display ~= nil then
|
||||
ui.display.handle_mouse(event)
|
||||
end
|
||||
end
|
||||
|
||||
return renderer
|
@ -4,6 +4,7 @@ local log = require("scada-common.log")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("rtu.databus")
|
||||
local modbus = require("rtu.modbus")
|
||||
|
||||
local rtu = {}
|
||||
@ -14,8 +15,6 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
|
||||
local println_ts = util.println_ts
|
||||
|
||||
-- create a new RTU unit
|
||||
---@nodiscard
|
||||
function rtu.init_unit()
|
||||
@ -325,6 +324,9 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
---@param units table RTU units
|
||||
---@param rtu_state rtu_state
|
||||
function public.handle_packet(packet, units, rtu_state)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println_ts(message) if not rtu_state.fp_ok then util.println_ts(message) end end
|
||||
|
||||
if packet.scada_frame.local_port() == local_port then
|
||||
-- check sequence number
|
||||
if self.r_seq_num == nil then
|
||||
@ -416,6 +418,9 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
end
|
||||
|
||||
self.last_est_ack = est_ack
|
||||
|
||||
-- report link state
|
||||
databus.tx_link_state(est_ack + 1)
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
require("/initenv").init_env()
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
@ -13,7 +14,9 @@ local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("rtu.config")
|
||||
local databus = require("rtu.databus")
|
||||
local modbus = require("rtu.modbus")
|
||||
local renderer = require("rtu.renderer")
|
||||
local rtu = require("rtu.rtu")
|
||||
local threads = require("rtu.threads")
|
||||
|
||||
@ -25,9 +28,10 @@ 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 = "v0.13.5"
|
||||
local RTU_VERSION = "v1.0.0"
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -71,6 +75,9 @@ local function main()
|
||||
-- startup
|
||||
----------------------------------------
|
||||
|
||||
-- record firmware versions and ID
|
||||
databus.tx_versions(RTU_VERSION, comms.version)
|
||||
|
||||
-- mount connected devices
|
||||
ppm.mount_all()
|
||||
|
||||
@ -79,6 +86,7 @@ local function main()
|
||||
-- RTU system state flags
|
||||
---@class rtu_state
|
||||
rtu_state = {
|
||||
fp_ok = false,
|
||||
linked = false,
|
||||
shutdown = false
|
||||
},
|
||||
@ -111,6 +119,8 @@ local function main()
|
||||
return
|
||||
end
|
||||
|
||||
databus.tx_hw_modem(true)
|
||||
|
||||
----------------------------------------
|
||||
-- interpret config and init units
|
||||
----------------------------------------
|
||||
@ -250,6 +260,8 @@ local function main()
|
||||
log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
||||
|
||||
unit.uid = #units
|
||||
|
||||
databus.tx_unit_hw_status(unit.uid, RTU_UNIT_HW_STATE.OK)
|
||||
end
|
||||
end
|
||||
|
||||
@ -409,6 +421,20 @@ local function main()
|
||||
log.info(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message))
|
||||
|
||||
rtu_unit.uid = #units
|
||||
|
||||
-- report hardware status
|
||||
if rtu_unit.type == RTU_UNIT_TYPE.VIRTUAL then
|
||||
databus.tx_unit_hw_status(rtu_unit.uid, RTU_UNIT_HW_STATE.OFFLINE)
|
||||
else
|
||||
if rtu_unit.is_multiblock then
|
||||
databus.tx_unit_hw_status(rtu_unit.uid, util.trinary(rtu_unit.formed == true, RTU_UNIT_HW_STATE.OK, RTU_UNIT_HW_STATE.UNFORMED))
|
||||
elseif faulted then
|
||||
databus.tx_unit_hw_status(rtu_unit.uid, RTU_UNIT_HW_STATE.FAULTED)
|
||||
else
|
||||
databus.tx_unit_hw_status(rtu_unit.uid, RTU_UNIT_HW_STATE.OK)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- we made it through all that trusting-user-to-write-a-config-file chaos
|
||||
@ -419,9 +445,23 @@ local function main()
|
||||
-- start system
|
||||
----------------------------------------
|
||||
|
||||
local rtu_state = __shared_memory.rtu_state
|
||||
|
||||
log.debug("boot> running configure()")
|
||||
|
||||
if configure() then
|
||||
-- start UI
|
||||
local message
|
||||
rtu_state.fp_ok, message = pcall(renderer.start_ui, units)
|
||||
|
||||
if not rtu_state.fp_ok then
|
||||
renderer.close_ui()
|
||||
println_ts(util.c("UI error: ", message))
|
||||
println("init> running without front panel")
|
||||
log.error(util.c("GUI crashed with error ", message))
|
||||
log.info("init> running in headless mode without front panel")
|
||||
end
|
||||
|
||||
-- start connection watchdog
|
||||
smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT)
|
||||
log.debug("startup> conn watchdog started")
|
||||
@ -451,6 +491,8 @@ local function main()
|
||||
println("configuration failed, exiting...")
|
||||
end
|
||||
|
||||
renderer.close_ui()
|
||||
|
||||
println_ts("exited")
|
||||
log.info("exited")
|
||||
end
|
||||
|
@ -4,6 +4,10 @@ local ppm = require("scada-common.ppm")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("rtu.databus")
|
||||
local modbus = require("rtu.modbus")
|
||||
local renderer = require("rtu.renderer")
|
||||
|
||||
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
||||
local envd_rtu = require("rtu.dev.envd_rtu")
|
||||
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
||||
@ -11,13 +15,12 @@ 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 modbus = require("rtu.modbus")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local threads = {}
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
|
||||
local println_ts = util.println_ts
|
||||
local UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||
|
||||
local MAIN_CLOCK = 2 -- (2Hz, 40 ticks)
|
||||
local COMMS_SLEEP = 100 -- (100ms, 2 ticks)
|
||||
@ -26,11 +29,15 @@ local COMMS_SLEEP = 100 -- (100ms, 2 ticks)
|
||||
---@nodiscard
|
||||
---@param smem rtu_shared_memory
|
||||
function threads.thread__main(smem)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println_ts(message) if not smem.rtu_state.fp_ok then util.println_ts(message) end end
|
||||
|
||||
---@class parallel_thread
|
||||
local public = {}
|
||||
|
||||
-- execute thread
|
||||
function public.exec()
|
||||
databus.tx_rt_status("main", true)
|
||||
log.debug("main thread start")
|
||||
|
||||
-- main loop clock
|
||||
@ -54,6 +61,9 @@ function threads.thread__main(smem)
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
|
||||
if event == "timer" and loop_clock.is_clock(param1) then
|
||||
-- blink heartbeat indicator
|
||||
databus.heartbeat()
|
||||
|
||||
-- start next clock timer
|
||||
loop_clock.start()
|
||||
|
||||
@ -82,6 +92,8 @@ function threads.thread__main(smem)
|
||||
if device == rtu_dev.modem then
|
||||
println_ts("wireless modem disconnected!")
|
||||
log.warning("comms modem disconnected!")
|
||||
|
||||
databus.tx_hw_modem(false)
|
||||
else
|
||||
log.warning("non-comms modem disconnected")
|
||||
end
|
||||
@ -91,10 +103,11 @@ function threads.thread__main(smem)
|
||||
if units[i].device == device then
|
||||
-- we are going to let the PPM prevent crashes
|
||||
-- return fault flags/codes to MODBUS queries
|
||||
local unit = units[i]
|
||||
local unit = units[i] ---@type rtu_unit_registry_entry
|
||||
local type_name = types.rtu_type_to_string(unit.type)
|
||||
println_ts(util.c("lost the ", type_name, " on interface ", unit.name))
|
||||
log.warning(util.c("lost the ", type_name, " unit peripheral on interface ", unit.name))
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.OFFLINE)
|
||||
break
|
||||
end
|
||||
end
|
||||
@ -113,6 +126,8 @@ function threads.thread__main(smem)
|
||||
|
||||
println_ts("wireless modem reconnected.")
|
||||
log.info("comms modem reconnected")
|
||||
|
||||
databus.tx_hw_modem(true)
|
||||
else
|
||||
log.info("wired modem reconnected")
|
||||
end
|
||||
@ -153,34 +168,49 @@ function threads.thread__main(smem)
|
||||
resend_advert = false
|
||||
log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")"))
|
||||
end
|
||||
|
||||
databus.tx_unit_hw_type(unit.uid, unit.type)
|
||||
end
|
||||
|
||||
if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||
unit.rtu = boilerv_rtu.new(device)
|
||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
||||
unit.formed = util.trinary(device.__p_is_faulted(), false, nil)
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.UNFORMED)
|
||||
elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
||||
unit.rtu = turbinev_rtu.new(device)
|
||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
||||
unit.formed = util.trinary(device.__p_is_faulted(), false, nil)
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.UNFORMED)
|
||||
elseif unit.type == RTU_UNIT_TYPE.IMATRIX then
|
||||
unit.rtu = imatrix_rtu.new(device)
|
||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
||||
unit.formed = util.trinary(device.__p_is_faulted(), false, nil)
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.UNFORMED)
|
||||
elseif unit.type == RTU_UNIT_TYPE.SPS then
|
||||
unit.rtu = sps_rtu.new(device)
|
||||
-- if not formed, indexing the multiblock functions would have resulted in a PPM fault
|
||||
unit.formed = util.trinary(device.__p_is_faulted(), false, nil)
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.UNFORMED)
|
||||
elseif unit.type == RTU_UNIT_TYPE.SNA then
|
||||
unit.rtu = sna_rtu.new(device)
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.OK)
|
||||
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||
unit.rtu = envd_rtu.new(device)
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.OK)
|
||||
else
|
||||
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
||||
end
|
||||
|
||||
if unit.is_multiblock and (unit.formed == false) then
|
||||
if unit.is_multiblock then
|
||||
if (unit.formed == false) then
|
||||
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.UNFORMED)
|
||||
end
|
||||
elseif device.__p_is_faulted() then
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.FAULTED)
|
||||
else
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.OK)
|
||||
end
|
||||
|
||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||
@ -199,6 +229,9 @@ function threads.thread__main(smem)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif event == "mouse_click" then
|
||||
-- handle a monitor touch event
|
||||
renderer.handle_mouse(core.events.click(param1, param2, param3))
|
||||
end
|
||||
|
||||
-- check for termination request
|
||||
@ -220,6 +253,8 @@ function threads.thread__main(smem)
|
||||
log.fatal(util.strval(result))
|
||||
end
|
||||
|
||||
databus.tx_rt_status("main", false)
|
||||
|
||||
if not rtu_state.shutdown then
|
||||
log.info("main thread restarting in 5 seconds...")
|
||||
util.psleep(5)
|
||||
@ -239,6 +274,7 @@ function threads.thread__comms(smem)
|
||||
|
||||
-- execute thread
|
||||
function public.exec()
|
||||
databus.tx_rt_status("comms", true)
|
||||
log.debug("comms thread start")
|
||||
|
||||
-- load in from shared memory
|
||||
@ -294,6 +330,8 @@ function threads.thread__comms(smem)
|
||||
log.fatal(util.strval(result))
|
||||
end
|
||||
|
||||
databus.tx_rt_status("comms", false)
|
||||
|
||||
if not rtu_state.shutdown then
|
||||
log.info("comms thread restarting in 5 seconds...")
|
||||
util.psleep(5)
|
||||
@ -314,7 +352,8 @@ function threads.thread__unit_comms(smem, unit)
|
||||
|
||||
-- execute thread
|
||||
function public.exec()
|
||||
log.debug(util.c("rtu unit thread start -> ", types.rtu_type_to_string(unit.type), "(", unit.name, ")"))
|
||||
databus.tx_rt_status("unit_" .. unit.uid, true)
|
||||
log.debug(util.c("rtu unit thread start -> ", types.rtu_type_to_string(unit.type), " (", unit.name, ")"))
|
||||
|
||||
-- load in from shared memory
|
||||
local rtu_state = smem.rtu_state
|
||||
@ -348,6 +387,13 @@ function threads.thread__unit_comms(smem, unit)
|
||||
-- received a packet
|
||||
local _, reply = unit.modbus_io.handle_packet(msg.message)
|
||||
rtu_comms.send_modbus(reply)
|
||||
|
||||
-- check if there was a problem and update the hardware state if so
|
||||
local frame = reply.get()
|
||||
if unit.formed and (bit.band(frame.func_code, types.MODBUS_FCODE.ERROR_FLAG) ~= 0) and
|
||||
(frame.data[1] == types.MODBUS_EXCODE.SERVER_DEVICE_FAIL) then
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.FAULTED)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -361,7 +407,14 @@ function threads.thread__unit_comms(smem, unit)
|
||||
|
||||
last_f_check = util.time_ms()
|
||||
|
||||
if unit.formed == nil then unit.formed = is_formed end
|
||||
if unit.formed == nil then
|
||||
unit.formed = is_formed
|
||||
if is_formed then databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.OK) end
|
||||
end
|
||||
|
||||
if not unit.formed then
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.UNFORMED)
|
||||
end
|
||||
|
||||
if (not unit.formed) and is_formed then
|
||||
-- newly re-formed
|
||||
@ -400,21 +453,25 @@ function threads.thread__unit_comms(smem, unit)
|
||||
unit.formed = device.isFormed()
|
||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||
else
|
||||
log.error("illegal remount of non-multiblock RTU attempted for " .. short_name, true)
|
||||
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
|
||||
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.UNFORMED)
|
||||
else
|
||||
rtu_comms.send_remounted(unit.uid)
|
||||
databus.tx_unit_hw_status(unit.uid, UNIT_HW_STATE.OK)
|
||||
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
|
||||
|
||||
log.info(util.c("reconnected the ", unit.type, " on interface ", unit.name))
|
||||
else
|
||||
log.error("failed to get interface of previously connected RTU unit " .. detail_name, true)
|
||||
end
|
||||
@ -444,8 +501,10 @@ function threads.thread__unit_comms(smem, unit)
|
||||
log.fatal(util.strval(result))
|
||||
end
|
||||
|
||||
databus.tx_rt_status("unit_" .. unit.uid, false)
|
||||
|
||||
if not rtu_state.shutdown then
|
||||
log.info(util.c("rtu unit thread ", types.rtu_type_to_string(unit.type), "(", unit.name, " restarting in 5 seconds..."))
|
||||
log.info(util.c("rtu unit thread ", types.rtu_type_to_string(unit.type), " (", unit.name, ") restarting in 5 seconds..."))
|
||||
util.psleep(5)
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user