#183 RTU front panel

This commit is contained in:
Mikayla Fischler 2023-04-20 20:40:28 -04:00
parent df45f6c984
commit d143015cc7
16 changed files with 471 additions and 45 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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
View 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
View 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

View File

@ -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

View File

@ -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

View File

@ -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