2022-05-04 17:37:01 +00:00
|
|
|
local log = require("scada-common.log")
|
2022-05-10 16:01:56 +00:00
|
|
|
local mqueue = require("scada-common.mqueue")
|
2022-05-04 17:37:01 +00:00
|
|
|
local ppm = require("scada-common.ppm")
|
|
|
|
local types = require("scada-common.types")
|
|
|
|
local util = require("scada-common.util")
|
|
|
|
|
2022-05-11 15:31:02 +00:00
|
|
|
local boiler_rtu = require("rtu.dev.boiler_rtu")
|
|
|
|
local boilerv_rtu = require("rtu.dev.boilerv_rtu")
|
|
|
|
local energymachine_rtu = require("rtu.dev.energymachine_rtu")
|
|
|
|
local imatrix_rtu = require("rtu.dev.imatrix_rtu")
|
|
|
|
local turbine_rtu = require("rtu.dev.turbine_rtu")
|
|
|
|
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
|
|
|
|
|
|
|
local modbus = require("rtu.modbus")
|
2022-05-04 17:37:01 +00:00
|
|
|
|
|
|
|
local threads = {}
|
|
|
|
|
|
|
|
local rtu_t = types.rtu_t
|
2022-04-27 16:46:04 +00:00
|
|
|
|
|
|
|
local print = util.print
|
|
|
|
local println = util.println
|
|
|
|
local print_ts = util.print_ts
|
|
|
|
local println_ts = util.println_ts
|
|
|
|
|
2022-04-27 21:59:25 +00:00
|
|
|
local MAIN_CLOCK = 2 -- (2Hz, 40 ticks)
|
2022-05-15 00:07:26 +00:00
|
|
|
local COMMS_SLEEP = 100 -- (100ms, 2 ticks)
|
2022-04-27 16:46:04 +00:00
|
|
|
|
|
|
|
-- main thread
|
2022-05-11 16:03:15 +00:00
|
|
|
---@param smem rtu_shared_memory
|
2022-05-04 17:37:01 +00:00
|
|
|
threads.thread__main = function (smem)
|
2022-05-21 17:56:14 +00:00
|
|
|
local public = {} ---@class thread
|
|
|
|
|
2022-04-27 16:46:04 +00:00
|
|
|
-- execute thread
|
2022-05-21 17:56:14 +00:00
|
|
|
public.exec = function ()
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("main thread start")
|
2022-04-27 19:52:34 +00:00
|
|
|
|
2022-05-10 17:06:13 +00:00
|
|
|
-- main loop clock
|
|
|
|
local loop_clock = util.new_clock(MAIN_CLOCK)
|
2022-04-27 16:46:04 +00:00
|
|
|
|
|
|
|
-- load in from shared memory
|
2022-04-29 14:19:05 +00:00
|
|
|
local rtu_state = smem.rtu_state
|
|
|
|
local rtu_dev = smem.rtu_dev
|
|
|
|
local rtu_comms = smem.rtu_sys.rtu_comms
|
2022-05-11 16:03:15 +00:00
|
|
|
local conn_watchdog = smem.rtu_sys.conn_watchdog
|
2022-04-29 14:19:05 +00:00
|
|
|
local units = smem.rtu_sys.units
|
2022-04-27 16:46:04 +00:00
|
|
|
|
2022-05-10 17:06:13 +00:00
|
|
|
-- start clock
|
|
|
|
loop_clock.start()
|
|
|
|
|
2022-04-27 16:46:04 +00:00
|
|
|
-- event loop
|
|
|
|
while true do
|
2022-05-10 16:01:56 +00:00
|
|
|
---@diagnostic disable-next-line: undefined-field
|
2022-04-27 16:46:04 +00:00
|
|
|
local event, param1, param2, param3, param4, param5 = os.pullEventRaw()
|
|
|
|
|
2022-05-10 17:06:13 +00:00
|
|
|
if event == "timer" and loop_clock.is_clock(param1) then
|
2022-04-29 14:19:05 +00:00
|
|
|
-- start next clock timer
|
2022-05-10 17:06:13 +00:00
|
|
|
loop_clock.start()
|
2022-04-29 14:19:05 +00:00
|
|
|
|
2022-05-10 17:06:13 +00:00
|
|
|
-- period tick, if we are not linked send advertisement
|
|
|
|
if not rtu_state.linked then
|
2022-04-29 14:19:05 +00:00
|
|
|
-- advertise units
|
|
|
|
rtu_comms.send_advertisement(units)
|
|
|
|
end
|
|
|
|
elseif event == "modem_message" then
|
|
|
|
-- got a packet
|
|
|
|
local packet = rtu_comms.parse_packet(param1, param2, param3, param4, param5)
|
|
|
|
if packet ~= nil then
|
|
|
|
-- pass the packet onto the comms message queue
|
|
|
|
smem.q.mq_comms.push_packet(packet)
|
|
|
|
end
|
2022-05-10 17:06:13 +00:00
|
|
|
elseif event == "timer" and conn_watchdog.is_timer(param1) then
|
2022-04-29 14:19:05 +00:00
|
|
|
-- haven't heard from server recently? unlink
|
|
|
|
rtu_comms.unlink(rtu_state)
|
|
|
|
elseif event == "peripheral_detach" then
|
2022-04-27 16:46:04 +00:00
|
|
|
-- handle loss of a device
|
2022-05-10 21:08:38 +00:00
|
|
|
local type, device = ppm.handle_unmount(param1)
|
|
|
|
|
|
|
|
if type ~= nil and device ~= nil then
|
|
|
|
if type == "modem" then
|
|
|
|
-- we only care if this is our wireless modem
|
|
|
|
if device == rtu_dev.modem then
|
|
|
|
println_ts("wireless modem disconnected!")
|
|
|
|
log.warning("comms modem disconnected!")
|
|
|
|
else
|
|
|
|
log.warning("non-comms modem disconnected")
|
|
|
|
end
|
2022-04-29 13:27:05 +00:00
|
|
|
else
|
2022-05-10 21:08:38 +00:00
|
|
|
for i = 1, #units do
|
|
|
|
-- find disconnected device
|
|
|
|
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]
|
|
|
|
println_ts("lost the " .. unit.type .. " on interface " .. unit.name)
|
|
|
|
end
|
2022-04-29 13:27:05 +00:00
|
|
|
end
|
2022-04-27 16:46:04 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif event == "peripheral" then
|
2022-04-29 13:27:05 +00:00
|
|
|
-- peripheral connect
|
2022-04-27 16:46:04 +00:00
|
|
|
local type, device = ppm.mount(param1)
|
|
|
|
|
2022-05-10 21:08:38 +00:00
|
|
|
if type ~= nil and device ~= nil then
|
|
|
|
if type == "modem" then
|
|
|
|
if device.isWireless() then
|
|
|
|
-- reconnected modem
|
|
|
|
rtu_dev.modem = device
|
|
|
|
rtu_comms.reconnect_modem(rtu_dev.modem)
|
|
|
|
|
|
|
|
println_ts("wireless modem reconnected.")
|
|
|
|
log.info("comms modem reconnected.")
|
|
|
|
else
|
|
|
|
log.info("wired modem reconnected.")
|
|
|
|
end
|
2022-04-29 13:27:05 +00:00
|
|
|
else
|
2022-05-10 21:08:38 +00:00
|
|
|
-- relink lost peripheral to correct unit entry
|
|
|
|
for i = 1, #units do
|
2022-05-11 16:03:15 +00:00
|
|
|
local unit = units[i] ---@type rtu_unit_registry_entry
|
2022-04-27 16:46:04 +00:00
|
|
|
|
2022-05-10 21:08:38 +00:00
|
|
|
-- find disconnected device to reconnect
|
|
|
|
if unit.name == param1 then
|
|
|
|
-- found, re-link
|
|
|
|
unit.device = device
|
|
|
|
|
|
|
|
if unit.type == rtu_t.boiler then
|
|
|
|
unit.rtu = boiler_rtu.new(device)
|
|
|
|
elseif unit.type == rtu_t.boiler_valve then
|
|
|
|
unit.rtu = boilerv_rtu.new(device)
|
|
|
|
elseif unit.type == rtu_t.turbine then
|
|
|
|
unit.rtu = turbine_rtu.new(device)
|
|
|
|
elseif unit.type == rtu_t.turbine_valve then
|
|
|
|
unit.rtu = turbinev_rtu.new(device)
|
|
|
|
elseif unit.type == rtu_t.energy_machine then
|
|
|
|
unit.rtu = energymachine_rtu.new(device)
|
|
|
|
elseif unit.type == rtu_t.induction_matrix then
|
|
|
|
unit.rtu = imatrix_rtu.new(device)
|
|
|
|
end
|
|
|
|
|
2022-05-11 16:03:15 +00:00
|
|
|
unit.modbus_io = modbus.new(unit.rtu, true)
|
2022-05-10 21:08:38 +00:00
|
|
|
|
|
|
|
println_ts("reconnected the " .. unit.type .. " on interface " .. unit.name)
|
|
|
|
end
|
2022-04-29 13:27:05 +00:00
|
|
|
end
|
2022-04-27 16:46:04 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check for termination request
|
|
|
|
if event == "terminate" or ppm.should_terminate() then
|
|
|
|
rtu_state.shutdown = true
|
2022-05-04 17:37:01 +00:00
|
|
|
log.info("terminate requested, main thread exiting")
|
2022-04-27 16:46:04 +00:00
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-05-21 17:56:14 +00:00
|
|
|
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
|
|
|
public.p_exec = function ()
|
|
|
|
local rtu_state = smem.rtu_state
|
|
|
|
|
|
|
|
while not rtu_state.shutdown do
|
|
|
|
local status, result = pcall(public.exec)
|
|
|
|
if status == false then
|
|
|
|
log.fatal(result)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not rtu_state.shutdown then
|
|
|
|
log.info("main thread restarting in 5 seconds...")
|
|
|
|
util.psleep(5)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return public
|
2022-04-27 16:46:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- communications handler thread
|
2022-05-11 16:03:15 +00:00
|
|
|
---@param smem rtu_shared_memory
|
2022-05-04 17:37:01 +00:00
|
|
|
threads.thread__comms = function (smem)
|
2022-05-21 17:56:14 +00:00
|
|
|
local public = {} ---@class thread
|
|
|
|
|
2022-04-27 16:46:04 +00:00
|
|
|
-- execute thread
|
2022-05-21 17:56:14 +00:00
|
|
|
public.exec = function ()
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("comms thread start")
|
2022-04-27 19:52:34 +00:00
|
|
|
|
2022-04-27 16:46:04 +00:00
|
|
|
-- load in from shared memory
|
2022-04-29 14:19:05 +00:00
|
|
|
local rtu_state = smem.rtu_state
|
|
|
|
local rtu_comms = smem.rtu_sys.rtu_comms
|
|
|
|
local units = smem.rtu_sys.units
|
2022-04-27 16:46:04 +00:00
|
|
|
|
2022-04-29 14:19:05 +00:00
|
|
|
local comms_queue = smem.q.mq_comms
|
2022-04-27 16:46:04 +00:00
|
|
|
|
2022-04-29 14:19:05 +00:00
|
|
|
local last_update = util.time()
|
2022-04-27 16:46:04 +00:00
|
|
|
|
|
|
|
-- thread loop
|
|
|
|
while true do
|
|
|
|
-- check for messages in the message queue
|
2022-04-29 13:27:05 +00:00
|
|
|
while comms_queue.ready() and not rtu_state.shutdown do
|
2022-04-27 16:46:04 +00:00
|
|
|
local msg = comms_queue.pop()
|
|
|
|
|
2022-05-11 16:03:15 +00:00
|
|
|
if msg ~= nil then
|
|
|
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
|
|
|
-- received a command
|
|
|
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
|
|
|
-- received data
|
|
|
|
elseif msg.qtype == mqueue.TYPE.PACKET then
|
|
|
|
-- received a packet
|
|
|
|
-- handle the packet (rtu_state passed to allow setting link flag)
|
|
|
|
rtu_comms.handle_packet(msg.message, units, rtu_state)
|
|
|
|
end
|
2022-04-27 16:46:04 +00:00
|
|
|
end
|
|
|
|
|
2022-04-27 21:59:25 +00:00
|
|
|
-- quick yield
|
|
|
|
util.nop()
|
2022-04-27 16:46:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- check for termination request
|
|
|
|
if rtu_state.shutdown then
|
2022-05-04 17:37:01 +00:00
|
|
|
rtu_comms.close(rtu_state)
|
|
|
|
log.info("comms thread exiting")
|
2022-04-27 16:46:04 +00:00
|
|
|
break
|
|
|
|
end
|
|
|
|
|
2022-04-27 23:06:01 +00:00
|
|
|
-- delay before next check
|
|
|
|
last_update = util.adaptive_delay(COMMS_SLEEP, last_update)
|
2022-04-27 16:46:04 +00:00
|
|
|
end
|
|
|
|
end
|
2022-04-27 19:52:34 +00:00
|
|
|
|
2022-05-21 17:56:14 +00:00
|
|
|
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
|
|
|
public.p_exec = function ()
|
|
|
|
local rtu_state = smem.rtu_state
|
|
|
|
|
|
|
|
while not rtu_state.shutdown do
|
|
|
|
local status, result = pcall(public.exec)
|
|
|
|
if status == false then
|
|
|
|
log.fatal(result)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not rtu_state.shutdown then
|
|
|
|
log.info("comms thread restarting in 5 seconds...")
|
|
|
|
util.psleep(5)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return public
|
2022-04-27 16:46:04 +00:00
|
|
|
end
|
2022-04-29 17:19:01 +00:00
|
|
|
|
|
|
|
-- per-unit communications handler thread
|
2022-05-11 16:03:15 +00:00
|
|
|
---@param smem rtu_shared_memory
|
|
|
|
---@param unit rtu_unit_registry_entry
|
2022-05-04 17:37:01 +00:00
|
|
|
threads.thread__unit_comms = function (smem, unit)
|
2022-05-21 17:56:14 +00:00
|
|
|
local public = {} ---@class thread
|
|
|
|
|
2022-04-29 17:19:01 +00:00
|
|
|
-- execute thread
|
2022-05-21 17:56:14 +00:00
|
|
|
public.exec = function ()
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")")
|
2022-04-29 17:19:01 +00:00
|
|
|
|
|
|
|
-- load in from shared memory
|
|
|
|
local rtu_state = smem.rtu_state
|
2022-05-10 16:01:56 +00:00
|
|
|
local rtu_comms = smem.rtu_sys.rtu_comms
|
2022-04-29 17:19:01 +00:00
|
|
|
local packet_queue = unit.pkt_queue
|
|
|
|
|
|
|
|
local last_update = util.time()
|
|
|
|
|
|
|
|
-- thread loop
|
|
|
|
while true do
|
|
|
|
-- check for messages in the message queue
|
|
|
|
while packet_queue.ready() and not rtu_state.shutdown do
|
|
|
|
local msg = packet_queue.pop()
|
|
|
|
|
2022-05-11 16:03:15 +00:00
|
|
|
if msg ~= nil then
|
|
|
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
|
|
|
-- received a command
|
|
|
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
|
|
|
-- received data
|
|
|
|
elseif msg.qtype == mqueue.TYPE.PACKET then
|
|
|
|
-- received a packet
|
|
|
|
local _, reply = unit.modbus_io.handle_packet(msg.message)
|
|
|
|
rtu_comms.send_modbus(reply)
|
|
|
|
end
|
2022-04-29 17:19:01 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- quick yield
|
|
|
|
util.nop()
|
|
|
|
end
|
|
|
|
|
|
|
|
-- check for termination request
|
|
|
|
if rtu_state.shutdown then
|
2022-05-04 17:37:01 +00:00
|
|
|
log.info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")")
|
2022-04-29 17:19:01 +00:00
|
|
|
break
|
|
|
|
end
|
|
|
|
|
|
|
|
-- delay before next check
|
|
|
|
last_update = util.adaptive_delay(COMMS_SLEEP, last_update)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-05-21 17:56:14 +00:00
|
|
|
-- execute the thread in a protected mode, retrying it on return if not shutting down
|
|
|
|
public.p_exec = function ()
|
|
|
|
local rtu_state = smem.rtu_state
|
|
|
|
|
|
|
|
while not rtu_state.shutdown do
|
|
|
|
local status, result = pcall(public.exec)
|
|
|
|
if status == false then
|
|
|
|
log.fatal(result)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not rtu_state.shutdown then
|
|
|
|
log.info("rtu unit thread " .. unit.name .. "(" .. unit.type .. ") restarting in 5 seconds...")
|
|
|
|
util.psleep(5)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return public
|
2022-04-29 17:19:01 +00:00
|
|
|
end
|
2022-05-04 17:37:01 +00:00
|
|
|
|
|
|
|
return threads
|