#33 lua module/require architecture changeover

This commit is contained in:
Mikayla Fischler 2022-05-04 13:37:01 -04:00
parent 7bcb260712
commit b575899d46
33 changed files with 679 additions and 518 deletions

View File

@ -1,4 +1,4 @@
-- #REQUIRES comms.lua local comms = require("scada-common.comms")
-- coordinator communications -- coordinator communications
function coord_comms() function coord_comms()

View File

@ -2,15 +2,14 @@
-- Nuclear Generation Facility SCADA Coordinator -- Nuclear Generation Facility SCADA Coordinator
-- --
os.loadAPI("scada-common/log.lua") local log = require("scada-common.log")
os.loadAPI("scada-common/util.lua") local ppm = require("scada-common.ppm")
os.loadAPI("scada-common/ppm.lua") local util = require("scada-common.util")
os.loadAPI("scada-common/comms.lua")
os.loadAPI("coordinator/config.lua") local config = require("config")
os.loadAPI("coordinator/coordinator.lua") local coordinator = require("coordinator")
local COORDINATOR_VERSION = "alpha-v0.1.1" local COORDINATOR_VERSION = "alpha-v0.1.2"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -19,9 +18,9 @@ local println_ts = util.println_ts
log.init("/log.txt", log.MODE.APPEND) log.init("/log.txt", log.MODE.APPEND)
log._info("========================================") log.info("========================================")
log._info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION)
log._info("========================================") log.info("========================================")
println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<")
-- mount connected devices -- mount connected devices

View File

@ -1,14 +1,18 @@
local config = {}
-- set to false to run in offline mode (safety regulation only) -- set to false to run in offline mode (safety regulation only)
NETWORKED = true config.NETWORKED = true
-- unique reactor ID -- unique reactor ID
REACTOR_ID = 1 config.REACTOR_ID = 1
-- port to send packets TO server -- port to send packets TO server
SERVER_PORT = 16000 config.SERVER_PORT = 16000
-- port to listen to incoming packets FROM server -- port to listen to incoming packets FROM server
LISTEN_PORT = 14001 config.LISTEN_PORT = 14001
-- log path -- log path
LOG_PATH = "/log.txt" config.LOG_PATH = "/log.txt"
-- log mode -- log mode
-- 0 = APPEND (adds to existing file on start) -- 0 = APPEND (adds to existing file on start)
-- 1 = NEW (replaces existing file on start) -- 1 = NEW (replaces existing file on start)
LOG_MODE = 0 config.LOG_MODE = 0
return config

View File

@ -1,7 +1,10 @@
-- #REQUIRES types.lua local comms = require("scada-common.comms")
-- #REQUIRES comms.lua local log = require("scada-common.log")
-- #REQUIRES ppm.lua local ppm = require("scada-common.ppm")
-- #REQUIRES util.lua local types = require("scada-common.types")
local util = require("scada-common.util")
local plc = {}
local iss_status_t = types.iss_status_t local iss_status_t = types.iss_status_t
@ -18,7 +21,7 @@ local println_ts = util.println_ts
-- Internal Safety System -- Internal Safety System
-- identifies dangerous states and SCRAMs reactor if warranted -- identifies dangerous states and SCRAMs reactor if warranted
-- autonomous from main SCADA supervisor/coordinator control -- autonomous from main SCADA supervisor/coordinator control
function iss_init(reactor) plc.iss_init = function (reactor)
local self = { local self = {
reactor = reactor, reactor = reactor,
cache = { false, false, false, false, false, false, false }, cache = { false, false, false, false, false, false, false },
@ -34,7 +37,7 @@ function iss_init(reactor)
local damage_percent = self.reactor.getDamagePercent() local damage_percent = self.reactor.getDamagePercent()
if damage_percent == ppm.ACCESS_FAULT then if damage_percent == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor damage") log.error("ISS: failed to check reactor damage")
return false return false
else else
return damage_percent >= 100 return damage_percent >= 100
@ -46,7 +49,7 @@ function iss_init(reactor)
local hc_needed = self.reactor.getHeatedCoolantNeeded() local hc_needed = self.reactor.getHeatedCoolantNeeded()
if hc_needed == ppm.ACCESS_FAULT then if hc_needed == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor heated coolant level") log.error("ISS: failed to check reactor heated coolant level")
return false return false
else else
return hc_needed == 0 return hc_needed == 0
@ -58,7 +61,7 @@ function iss_init(reactor)
local w_needed = self.reactor.getWasteNeeded() local w_needed = self.reactor.getWasteNeeded()
if w_needed == ppm.ACCESS_FAULT then if w_needed == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor waste level") log.error("ISS: failed to check reactor waste level")
return false return false
else else
return w_needed == 0 return w_needed == 0
@ -71,7 +74,7 @@ function iss_init(reactor)
local temp = self.reactor.getTemperature() local temp = self.reactor.getTemperature()
if temp == ppm.ACCESS_FAULT then if temp == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor temperature") log.error("ISS: failed to check reactor temperature")
return false return false
else else
return temp >= 1200 return temp >= 1200
@ -83,7 +86,7 @@ function iss_init(reactor)
local fuel = self.reactor.getFuel() local fuel = self.reactor.getFuel()
if fuel == ppm.ACCESS_FAULT then if fuel == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor fuel level") log.error("ISS: failed to check reactor fuel level")
return false return false
else else
return fuel == 0 return fuel == 0
@ -95,7 +98,7 @@ function iss_init(reactor)
local coolant_filled = self.reactor.getCoolantFilledPercentage() local coolant_filled = self.reactor.getCoolantFilledPercentage()
if coolant_filled == ppm.ACCESS_FAULT then if coolant_filled == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor coolant level") log.error("ISS: failed to check reactor coolant level")
return false return false
else else
return coolant_filled < 0.02 return coolant_filled < 0.02
@ -134,25 +137,25 @@ function iss_init(reactor)
if self.tripped then if self.tripped then
status = self.trip_cause status = self.trip_cause
elseif self.cache[1] then elseif self.cache[1] then
log._warning("ISS: damage critical!") log.warning("ISS: damage critical!")
status = iss_status_t.dmg_crit status = iss_status_t.dmg_crit
elseif self.cache[4] then elseif self.cache[4] then
log._warning("ISS: high temperature!") log.warning("ISS: high temperature!")
status = iss_status_t.high_temp status = iss_status_t.high_temp
elseif self.cache[2] then elseif self.cache[2] then
log._warning("ISS: heated coolant backup!") log.warning("ISS: heated coolant backup!")
status = iss_status_t.ex_hcoolant status = iss_status_t.ex_hcoolant
elseif self.cache[6] then elseif self.cache[6] then
log._warning("ISS: no coolant!") log.warning("ISS: no coolant!")
status = iss_status_t.no_coolant status = iss_status_t.no_coolant
elseif self.cache[3] then elseif self.cache[3] then
log._warning("ISS: full waste!") log.warning("ISS: full waste!")
status = iss_status_t.ex_waste status = iss_status_t.ex_waste
elseif self.cache[5] then elseif self.cache[5] then
log._warning("ISS: no fuel!") log.warning("ISS: no fuel!")
status = iss_status_t.no_fuel status = iss_status_t.no_fuel
elseif self.cache[7] then elseif self.cache[7] then
log._warning("ISS: supervisor connection timeout!") log.warning("ISS: supervisor connection timeout!")
status = iss_status_t.timeout status = iss_status_t.timeout
else else
self.tripped = false self.tripped = false
@ -161,7 +164,7 @@ function iss_init(reactor)
-- if a new trip occured... -- if a new trip occured...
local first_trip = false local first_trip = false
if not was_tripped and status ~= iss_status_t.ok then if not was_tripped and status ~= iss_status_t.ok then
log._warning("ISS: reactor SCRAM") log.warning("ISS: reactor SCRAM")
first_trip = true first_trip = true
self.tripped = true self.tripped = true
@ -169,7 +172,7 @@ function iss_init(reactor)
self.reactor.scram() self.reactor.scram()
if self.reactor.__p_is_faulted() then if self.reactor.__p_is_faulted() then
log._error("ISS: failed reactor SCRAM") log.error("ISS: failed reactor SCRAM")
end end
end end
@ -198,7 +201,7 @@ function iss_init(reactor)
end end
-- reactor PLC communications -- reactor PLC communications
function comms_init(id, modem, local_port, server_port, reactor, iss) plc.comms = function (id, modem, local_port, server_port, reactor, iss)
local self = { local self = {
id = id, id = id,
seq_num = 0, seq_num = 0,
@ -355,7 +358,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
if not self.reactor.__p_is_faulted() then if not self.reactor.__p_is_faulted() then
_send(RPLC_TYPES.MEK_STRUCT, mek_data) _send(RPLC_TYPES.MEK_STRUCT, mek_data)
else else
log._error("failed to send structure: PPM fault") log.error("failed to send structure: PPM fault")
end end
end end
@ -417,7 +420,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
if not self.reactor.__p_is_faulted() then if not self.reactor.__p_is_faulted() then
_send(RPLC_TYPES.STATUS, sys_status) _send(RPLC_TYPES.STATUS, sys_status)
else else
log._error("failed to send status: PPM fault") log.error("failed to send status: PPM fault")
end end
end end
end end
@ -463,7 +466,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
pkt = mgmt_pkt.get() pkt = mgmt_pkt.get()
end end
else else
log._error("illegal packet type " .. s_pkt.protocol(), true) log.error("illegal packet type " .. s_pkt.protocol(), true)
end end
end end
@ -477,7 +480,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
if self.r_seq_num == nil then if self.r_seq_num == nil then
self.r_seq_num = packet.scada_frame.seq_num() self.r_seq_num = packet.scada_frame.seq_num()
elseif self.linked and self.r_seq_num >= packet.scada_frame.seq_num() then elseif self.linked and self.r_seq_num >= packet.scada_frame.seq_num() then
log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return return
else else
self.r_seq_num = packet.scada_frame.seq_num() self.r_seq_num = packet.scada_frame.seq_num()
@ -496,19 +499,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
local trip_time = util.time() - timestamp local trip_time = util.time() - timestamp
if trip_time > 500 then if trip_time > 500 then
log._warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")") log.warning("PLC KEEP_ALIVE trip time > 500ms (" .. trip_time .. ")")
end end
-- log._debug("RPLC RTT = ".. trip_time .. "ms") -- log.debug("RPLC RTT = ".. trip_time .. "ms")
_send_keep_alive_ack(timestamp) _send_keep_alive_ack(timestamp)
else else
log._debug("RPLC keep alive packet length mismatch") log.debug("RPLC keep alive packet length mismatch")
end end
elseif packet.type == RPLC_TYPES.LINK_REQ then elseif packet.type == RPLC_TYPES.LINK_REQ then
-- link request confirmation -- link request confirmation
if packet.length == 1 then if packet.length == 1 then
log._debug("received unsolicited link request response") log.debug("received unsolicited link request response")
local link_ack = packet.data[1] local link_ack = packet.data[1]
@ -516,31 +519,31 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
self.status_cache = nil self.status_cache = nil
_send_struct() _send_struct()
send_status(plc_state.degraded) send_status(plc_state.degraded)
log._debug("re-sent initial status data") log.debug("re-sent initial status data")
elseif link_ack == RPLC_LINKING.DENY then elseif link_ack == RPLC_LINKING.DENY then
println_ts("received unsolicited link denial, unlinking") println_ts("received unsolicited link denial, unlinking")
log._debug("unsolicited RPLC link request denied") log.debug("unsolicited RPLC link request denied")
elseif link_ack == RPLC_LINKING.COLLISION then elseif link_ack == RPLC_LINKING.COLLISION then
println_ts("received unsolicited link collision, unlinking") println_ts("received unsolicited link collision, unlinking")
log._warning("unsolicited RPLC link request collision") log.warning("unsolicited RPLC link request collision")
else else
println_ts("invalid unsolicited link response") println_ts("invalid unsolicited link response")
log._error("unsolicited unknown RPLC link request response") log.error("unsolicited unknown RPLC link request response")
end end
self.linked = link_ack == RPLC_LINKING.ALLOW self.linked = link_ack == RPLC_LINKING.ALLOW
else else
log._debug("RPLC link req packet length mismatch") log.debug("RPLC link req packet length mismatch")
end end
elseif packet.type == RPLC_TYPES.STATUS then elseif packet.type == RPLC_TYPES.STATUS then
-- request of full status, clear cache first -- request of full status, clear cache first
self.status_cache = nil self.status_cache = nil
send_status(plc_state.degraded) send_status(plc_state.degraded)
log._debug("sent out status cache again, did supervisor miss it?") log.debug("sent out status cache again, did supervisor miss it?")
elseif packet.type == RPLC_TYPES.MEK_STRUCT then elseif packet.type == RPLC_TYPES.MEK_STRUCT then
-- request for physical structure -- request for physical structure
_send_struct() _send_struct()
log._debug("sent out structure again, did supervisor miss it?") log.debug("sent out structure again, did supervisor miss it?")
elseif packet.type == RPLC_TYPES.MEK_SCRAM then elseif packet.type == RPLC_TYPES.MEK_SCRAM then
-- disable the reactor -- disable the reactor
self.scrammed = true self.scrammed = true
@ -576,14 +579,14 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
_send_ack(packet.type, success) _send_ack(packet.type, success)
else else
log._debug("RPLC set burn rate packet length mismatch") log.debug("RPLC set burn rate packet length mismatch")
end end
elseif packet.type == RPLC_TYPES.ISS_CLEAR then elseif packet.type == RPLC_TYPES.ISS_CLEAR then
-- clear the ISS status -- clear the ISS status
iss.reset() iss.reset()
_send_ack(packet.type, true) _send_ack(packet.type, true)
else else
log._warning("received unknown RPLC packet type " .. packet.type) log.warning("received unknown RPLC packet type " .. packet.type)
end end
elseif packet.type == RPLC_TYPES.LINK_REQ then elseif packet.type == RPLC_TYPES.LINK_REQ then
-- link request confirmation -- link request confirmation
@ -592,7 +595,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
if link_ack == RPLC_LINKING.ALLOW then if link_ack == RPLC_LINKING.ALLOW then
println_ts("linked!") println_ts("linked!")
log._debug("RPLC link request approved") log.debug("RPLC link request approved")
-- reset remote sequence number and cache -- reset remote sequence number and cache
self.r_seq_num = nil self.r_seq_num = nil
@ -601,24 +604,24 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
_send_struct() _send_struct()
send_status(plc_state.degraded) send_status(plc_state.degraded)
log._debug("sent initial status data") log.debug("sent initial status data")
elseif link_ack == RPLC_LINKING.DENY then elseif link_ack == RPLC_LINKING.DENY then
println_ts("link request denied, retrying...") println_ts("link request denied, retrying...")
log._debug("RPLC link request denied") log.debug("RPLC link request denied")
elseif link_ack == RPLC_LINKING.COLLISION then elseif link_ack == RPLC_LINKING.COLLISION then
println_ts("reactor PLC ID collision (check config), retrying...") println_ts("reactor PLC ID collision (check config), retrying...")
log._warning("RPLC link request collision") log.warning("RPLC link request collision")
else else
println_ts("invalid link response, bad channel? retrying...") println_ts("invalid link response, bad channel? retrying...")
log._error("unknown RPLC link request response") log.error("unknown RPLC link request response")
end end
self.linked = link_ack == RPLC_LINKING.ALLOW self.linked = link_ack == RPLC_LINKING.ALLOW
else else
log._debug("RPLC link req packet length mismatch") log.debug("RPLC link req packet length mismatch")
end end
else else
log._debug("discarding non-link packet before linked") log.debug("discarding non-link packet before linked")
end end
elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then
-- handle session close -- handle session close
@ -626,9 +629,9 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
conn_watchdog.cancel() conn_watchdog.cancel()
unlink() unlink()
println_ts("server connection closed by remote host") println_ts("server connection closed by remote host")
log._warning("server connection closed by remote host") log.warning("server connection closed by remote host")
else else
log._warning("received unknown SCADA_MGMT packet type " .. packet.type) log.warning("received unknown SCADA_MGMT packet type " .. packet.type)
end end
end end
end end
@ -652,3 +655,5 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
is_linked = is_linked is_linked = is_linked
} }
end end
return plc

View File

@ -2,18 +2,16 @@
-- Reactor Programmable Logic Controller -- Reactor Programmable Logic Controller
-- --
os.loadAPI("scada-common/log.lua") local log = require("scada-common.log")
os.loadAPI("scada-common/types.lua") local mqueue = require("scada-common.mqueue")
os.loadAPI("scada-common/util.lua") local ppm = require("scada-common.ppm")
os.loadAPI("scada-common/ppm.lua") local util = require("scada-common.util")
os.loadAPI("scada-common/comms.lua")
os.loadAPI("scada-common/mqueue.lua")
os.loadAPI("config.lua") local config = require("config")
os.loadAPI("plc.lua") local plc = require("plc")
os.loadAPI("threads.lua") local threads = require("threads")
local R_PLC_VERSION = "alpha-v0.5.2" local R_PLC_VERSION = "alpha-v0.6.0"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -22,9 +20,9 @@ local println_ts = util.println_ts
log.init(config.LOG_PATH, config.LOG_MODE) log.init(config.LOG_PATH, config.LOG_MODE)
log._info("========================================") log.info("========================================")
log._info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION)
log._info("========================================") log.info("========================================")
println(">> Reactor PLC " .. R_PLC_VERSION .. " <<") println(">> Reactor PLC " .. R_PLC_VERSION .. " <<")
-- mount connected devices -- mount connected devices
@ -78,7 +76,7 @@ local plc_state = __shared_memory.plc_state
-- we need a reactor and a modem -- we need a reactor and a modem
if smem_dev.reactor == nil then if smem_dev.reactor == nil then
println("boot> fission reactor not found"); println("boot> fission reactor not found");
log._warning("no reactor on startup") log.warning("no reactor on startup")
plc_state.init_ok = false plc_state.init_ok = false
plc_state.degraded = true plc_state.degraded = true
@ -86,7 +84,7 @@ if smem_dev.reactor == nil then
end end
if networked and smem_dev.modem == nil then if networked and smem_dev.modem == nil then
println("boot> wireless modem not found") println("boot> wireless modem not found")
log._warning("no wireless modem on startup") log.warning("no wireless modem on startup")
if smem_dev.reactor ~= nil then if smem_dev.reactor ~= nil then
smem_dev.reactor.scram() smem_dev.reactor.scram()
@ -104,19 +102,19 @@ function init()
-- init internal safety system -- init internal safety system
smem_sys.iss = plc.iss_init(smem_dev.reactor) smem_sys.iss = plc.iss_init(smem_dev.reactor)
log._debug("iss init") log.debug("iss init")
if __shared_memory.networked then if __shared_memory.networked then
-- start comms -- start comms
smem_sys.plc_comms = plc.comms_init(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss) smem_sys.plc_comms = plc.comms(config.REACTOR_ID, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, smem_dev.reactor, smem_sys.iss)
log._debug("comms init") log.debug("comms init")
-- comms watchdog, 3 second timeout -- comms watchdog, 3 second timeout
smem_sys.conn_watchdog = util.new_watchdog(3) smem_sys.conn_watchdog = util.new_watchdog(3)
log._debug("conn watchdog started") log.debug("conn watchdog started")
else else
println("boot> starting in offline mode"); println("boot> starting in offline mode");
log._debug("running without networking") log.debug("running without networking")
end end
os.queueEvent("clock_start") os.queueEvent("clock_start")
@ -124,7 +122,7 @@ function init()
println("boot> completed"); println("boot> completed");
else else
println("boot> system in degraded state, awaiting devices...") println("boot> system in degraded state, awaiting devices...")
log._warning("booted in a degraded state, awaiting peripheral connections...") log.warning("booted in a degraded state, awaiting peripheral connections...")
end end
end end
@ -148,11 +146,11 @@ if __shared_memory.networked then
if plc_state.init_ok then if plc_state.init_ok then
-- send status one last time after ISS shutdown -- send status one last time after ISS shutdown
plc_comms.send_status(plc_state.degraded) smem_sys.plc_comms.send_status(plc_state.degraded)
plc_comms.send_iss_status() smem_sys.plc_comms.send_iss_status()
-- close connection -- close connection
plc_comms.close(conn_watchdog) smem_sys.plc_comms.close(smem_sys.conn_watchdog)
end end
else else
-- run threads, excluding comms -- run threads, excluding comms
@ -160,4 +158,4 @@ else
end end
println_ts("exited") println_ts("exited")
log._info("exited") log.info("exited")

View File

@ -1,7 +1,9 @@
-- #REQUIRES comms.lua local log = require("scada-common.log")
-- #REQUIRES log.lua local mqueue = require("scada-common.mqueue")
-- #REQUIRES ppm.lua local ppm = require("scada-common.ppm")
-- #REQUIRES util.lua local util = require("scada-common.util")
local threads = {}
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -28,10 +30,10 @@ local MQ__COMM_CMD = {
} }
-- main thread -- main thread
function thread__main(smem, init) threads.thread__main = function (smem, init)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("main thread init, clock inactive") log.debug("main thread init, clock inactive")
-- send status updates at 2Hz (every 10 server ticks) (every loop tick) -- send status updates at 2Hz (every 10 server ticks) (every loop tick)
-- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks) -- send link requests at 0.5Hz (every 40 server ticks) (every 4 loop ticks)
@ -89,14 +91,14 @@ function thread__main(smem, init)
if device.type == "fissionReactor" then if device.type == "fissionReactor" then
println_ts("reactor disconnected!") println_ts("reactor disconnected!")
log._error("reactor disconnected!") log.error("reactor disconnected!")
plc_state.no_reactor = true plc_state.no_reactor = true
plc_state.degraded = true plc_state.degraded = true
elseif networked and device.type == "modem" then elseif networked and device.type == "modem" then
-- we only care if this is our wireless modem -- we only care if this is our wireless modem
if device.dev == plc_dev.modem then if device.dev == plc_dev.modem then
println_ts("wireless modem disconnected!") println_ts("wireless modem disconnected!")
log._error("comms modem disconnected!") log.error("comms modem disconnected!")
plc_state.no_modem = true plc_state.no_modem = true
if plc_state.init_ok then if plc_state.init_ok then
@ -106,7 +108,7 @@ function thread__main(smem, init)
plc_state.degraded = true plc_state.degraded = true
else else
log._warning("non-comms modem disconnected") log.warning("non-comms modem disconnected")
end end
end end
elseif event == "peripheral" then elseif event == "peripheral" then
@ -120,7 +122,7 @@ function thread__main(smem, init)
smem.q.mq_iss.push_command(MQ__ISS_CMD.SCRAM) smem.q.mq_iss.push_command(MQ__ISS_CMD.SCRAM)
println_ts("reactor reconnected.") println_ts("reactor reconnected.")
log._info("reactor reconnected.") log.info("reactor reconnected.")
plc_state.no_reactor = false plc_state.no_reactor = false
if plc_state.init_ok then if plc_state.init_ok then
@ -144,7 +146,7 @@ function thread__main(smem, init)
end end
println_ts("wireless modem reconnected.") println_ts("wireless modem reconnected.")
log._info("comms modem reconnected.") log.info("comms modem reconnected.")
plc_state.no_modem = false plc_state.no_modem = false
-- determine if we are still in a degraded state -- determine if we are still in a degraded state
@ -152,7 +154,7 @@ function thread__main(smem, init)
plc_state.degraded = false plc_state.degraded = false
end end
else else
log._info("wired modem reconnected.") log.info("wired modem reconnected.")
end end
end end
@ -163,12 +165,12 @@ function thread__main(smem, init)
elseif event == "clock_start" then elseif event == "clock_start" then
-- start loop clock -- start loop clock
loop_clock = os.startTimer(MAIN_CLOCK) loop_clock = os.startTimer(MAIN_CLOCK)
log._debug("main thread clock started") log.debug("main thread clock started")
end end
-- check for termination request -- check for termination request
if event == "terminate" or ppm.should_terminate() then if event == "terminate" or ppm.should_terminate() then
log._info("terminate requested, main thread exiting") log.info("terminate requested, main thread exiting")
-- iss handles reactor shutdown -- iss handles reactor shutdown
plc_state.shutdown = true plc_state.shutdown = true
break break
@ -180,10 +182,10 @@ function thread__main(smem, init)
end end
-- ISS monitor thread -- ISS monitor thread
function thread__iss(smem) threads.thread__iss = function (smem)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("iss thread start") log.debug("iss thread start")
-- load in from shared memory -- load in from shared memory
local networked = smem.networked local networked = smem.networked
@ -257,17 +259,17 @@ function thread__iss(smem)
plc_state.scram = true plc_state.scram = true
if reactor.scram() then if reactor.scram() then
println_ts("successful reactor SCRAM") println_ts("successful reactor SCRAM")
log._error("successful reactor SCRAM") log.error("successful reactor SCRAM")
else else
println_ts("failed reactor SCRAM") println_ts("failed reactor SCRAM")
log._error("failed reactor SCRAM") log.error("failed reactor SCRAM")
end end
elseif msg.message == MQ__ISS_CMD.TRIP_TIMEOUT then elseif msg.message == MQ__ISS_CMD.TRIP_TIMEOUT then
-- watchdog tripped -- watchdog tripped
plc_state.scram = true plc_state.scram = true
iss.trip_timeout() iss.trip_timeout()
println_ts("server timeout") println_ts("server timeout")
log._warning("server timeout") log.warning("server timeout")
end end
elseif msg.qtype == mqueue.TYPE.DATA then elseif msg.qtype == mqueue.TYPE.DATA then
-- received data -- received data
@ -282,19 +284,19 @@ function thread__iss(smem)
-- check for termination request -- check for termination request
if plc_state.shutdown then if plc_state.shutdown then
-- safe exit -- safe exit
log._info("iss thread shutdown initiated") log.info("iss thread shutdown initiated")
if plc_state.init_ok then if plc_state.init_ok then
plc_state.scram = true plc_state.scram = true
reactor.scram() reactor.scram()
if reactor.__p_is_ok() then if reactor.__p_is_ok() then
println_ts("reactor disabled") println_ts("reactor disabled")
log._info("iss thread reactor SCRAM OK") log.info("iss thread reactor SCRAM OK")
else else
println_ts("exiting, reactor failed to disable") println_ts("exiting, reactor failed to disable")
log._error("iss thread failed to SCRAM reactor on exit") log.error("iss thread failed to SCRAM reactor on exit")
end end
end end
log._info("iss thread exiting") log.info("iss thread exiting")
break break
end end
@ -307,10 +309,10 @@ function thread__iss(smem)
end end
-- communications sender thread -- communications sender thread
function thread__comms_tx(smem) threads.thread__comms_tx = function (smem)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("comms tx thread start") log.debug("comms tx thread start")
-- load in from shared memory -- load in from shared memory
local plc_state = smem.plc_state local plc_state = smem.plc_state
@ -345,7 +347,7 @@ function thread__comms_tx(smem)
-- check for termination request -- check for termination request
if plc_state.shutdown then if plc_state.shutdown then
log._info("comms tx thread exiting") log.info("comms tx thread exiting")
break break
end end
@ -358,10 +360,10 @@ function thread__comms_tx(smem)
end end
-- communications handler thread -- communications handler thread
function thread__comms_rx(smem) threads.thread__comms_rx = function (smem)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("comms rx thread start") log.debug("comms rx thread start")
-- load in from shared memory -- load in from shared memory
local plc_state = smem.plc_state local plc_state = smem.plc_state
@ -397,7 +399,7 @@ function thread__comms_rx(smem)
-- check for termination request -- check for termination request
if plc_state.shutdown then if plc_state.shutdown then
log._info("comms rx thread exiting") log.info("comms rx thread exiting")
break break
end end
@ -410,10 +412,10 @@ function thread__comms_rx(smem)
end end
-- apply setpoints -- apply setpoints
function thread__setpoint_control(smem) threads.thread__setpoint_control = function (smem)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("setpoint control thread start") log.debug("setpoint control thread start")
-- load in from shared memory -- load in from shared memory
local plc_state = smem.plc_state local plc_state = smem.plc_state
@ -434,10 +436,10 @@ function thread__setpoint_control(smem)
if not plc_state.scram then if not plc_state.scram then
if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then if math.abs(setpoints.burn_rate - last_sp_burn) <= 5 then
-- update without ramp if <= 5 mB/t change -- update without ramp if <= 5 mB/t change
log._debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t") log.debug("setting burn rate directly to " .. setpoints.burn_rate .. "mB/t")
reactor.setBurnRate(setpoints.burn_rate) reactor.setBurnRate(setpoints.burn_rate)
else else
log._debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t") log.debug("starting burn rate ramp from " .. last_sp_burn .. "mB/t to " .. setpoints.burn_rate .. "mB/t")
running = true running = true
end end
@ -489,7 +491,7 @@ function thread__setpoint_control(smem)
-- check for termination request -- check for termination request
if plc_state.shutdown then if plc_state.shutdown then
log._info("setpoint control thread exiting") log.info("setpoint control thread exiting")
break break
end end
@ -500,3 +502,5 @@ function thread__setpoint_control(smem)
return { exec = exec } return { exec = exec }
end end
return threads

View File

@ -1,47 +1,49 @@
-- #REQUIRES rsio.lua local rsio = require("scada-common.rsio")
local config = {}
-- port to send packets TO server -- port to send packets TO server
SERVER_PORT = 16000 config.SERVER_PORT = 16000
-- port to listen to incoming packets FROM server -- port to listen to incoming packets FROM server
LISTEN_PORT = 15001 config.LISTEN_PORT = 15001
-- log path -- log path
LOG_PATH = "/log.txt" config.LOG_PATH = "/log.txt"
-- log mode -- log mode
-- 0 = APPEND (adds to existing file on start) -- 0 = APPEND (adds to existing file on start)
-- 1 = NEW (replaces existing file on start) -- 1 = NEW (replaces existing file on start)
LOG_MODE = 0 config.LOG_MODE = 0
-- RTU peripheral devices (named: side/network device name) -- RTU peripheral devices (named: side/network device name)
RTU_DEVICES = { config.RTU_DEVICES = {
{ {
name = "boiler_0", name = "boiler_1",
index = 1, index = 1,
for_reactor = 1 for_reactor = 1
}, },
{ {
name = "turbine_0", name = "turbine_1",
index = 1, index = 1,
for_reactor = 1 for_reactor = 1
} }
} }
-- RTU redstone interface definitions -- RTU redstone interface definitions
RTU_REDSTONE = { config.RTU_REDSTONE = {
{ {
for_reactor = 1, for_reactor = 1,
io = { io = {
{ {
channel = rsio.RS_IO.WASTE_PO, channel = rsio.IO.WASTE_PO,
side = "top", side = "top",
bundled_color = colors.blue, bundled_color = colors.blue,
for_reactor = 1 for_reactor = 1
}, },
{ {
channel = rsio.RS_IO.WASTE_PU, channel = rsio.IO.WASTE_PU,
side = "top", side = "top",
bundled_color = colors.cyan, bundled_color = colors.cyan,
for_reactor = 1 for_reactor = 1
}, },
{ {
channel = rsio.RS_IO.WASTE_AM, channel = rsio.IO.WASTE_AM,
side = "top", side = "top",
bundled_color = colors.purple, bundled_color = colors.purple,
for_reactor = 1 for_reactor = 1
@ -49,3 +51,5 @@ RTU_REDSTONE = {
} }
} }
} }
return config

View File

@ -1,8 +1,10 @@
-- #REQUIRES rtu.lua local rtu = require("rtu")
function new(boiler) local boiler_rtu = {}
boiler_rtu.new = function (boiler)
local self = { local self = {
rtu = rtu.rtu_init(), rtu = rtu.init_unit(),
boiler = boiler boiler = boiler
} }
@ -49,3 +51,5 @@ function new(boiler)
rtu_interface = rtu_interface rtu_interface = rtu_interface
} }
end end
return boiler_rtu

View File

@ -1,8 +1,10 @@
-- #REQUIRES rtu.lua local rtu = require("rtu")
function new(boiler) local boilerv_rtu = {}
boilerv_rtu.new = function (boiler)
local self = { local self = {
rtu = rtu.rtu_init(), rtu = rtu.init_unit(),
boiler = boiler boiler = boiler
} }
@ -54,3 +56,5 @@ function new(boiler)
rtu_interface = rtu_interface rtu_interface = rtu_interface
} }
end end
return boilerv_rtu

View File

@ -1,8 +1,10 @@
-- #REQUIRES rtu.lua local rtu = require("rtu")
function new(machine) local energymachine_rtu = {}
energymachine_rtu.new = function (machine)
local self = { local self = {
rtu = rtu.rtu_init(), rtu = rtu.init_unit(),
machine = machine machine = machine
} }
@ -31,3 +33,5 @@ function new(machine)
rtu_interface = rtu_interface rtu_interface = rtu_interface
} }
end end
return energymachine_rtu

View File

@ -1,8 +1,10 @@
-- #REQUIRES rtu.lua local rtu = require("rtu")
function new(imatrix) local imatrix_rtu = {}
imatrix_rtu.new = function (imatrix)
local self = { local self = {
rtu = rtu.rtu_init(), rtu = rtu.init_unit(),
imatrix = imatrix imatrix = imatrix
} }
@ -42,3 +44,5 @@ function new(imatrix)
rtu_interface = rtu_interface rtu_interface = rtu_interface
} }
end end
return imatrix_rtu

View File

@ -1,13 +1,14 @@
-- #REQUIRES rtu.lua local rtu = require("rtu")
-- #REQUIRES rsio.lua local rsio = require("scada-common.rsio")
-- note: this RTU makes extensive use of the programming concept of closures
local redstone_rtu = {}
local digital_read = rsio.digital_read local digital_read = rsio.digital_read
local digital_is_active = rsio.digital_is_active local digital_is_active = rsio.digital_is_active
function new() redstone_rtu.new = function ()
local self = { local self = {
rtu = rtu.rtu_init() rtu = rtu.init_unit()
} }
local rtu_interface = function () local rtu_interface = function ()
@ -91,3 +92,5 @@ function new()
link_ao = link_ao link_ao = link_ao
} }
end end
return redstone_rtu

View File

@ -1,8 +1,10 @@
-- #REQUIRES rtu.lua local rtu = require("rtu")
function new(turbine) local turbine_rtu = {}
turbine_rtu.new = function (turbine)
local self = { local self = {
rtu = rtu.rtu_init(), rtu = rtu.init_unit(),
turbine = turbine turbine = turbine
} }
@ -44,3 +46,5 @@ function new(turbine)
rtu_interface = rtu_interface rtu_interface = rtu_interface
} }
end end
return turbine_rtu

View File

@ -1,8 +1,10 @@
-- #REQUIRES rtu.lua local rtu = require("rtu")
function new(turbine) local turbinev_rtu = {}
turbinev_rtu.new = function (turbine)
local self = { local self = {
rtu = rtu.rtu_init(), rtu = rtu.init_unit(),
turbine = turbine turbine = turbine
} }
@ -14,8 +16,8 @@ function new(turbine)
-- none -- none
-- coils -- -- coils --
self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end), function () end) self.rtu.connect_coil(function () self.turbine.incrementDumpingMode() end, function () end)
self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end), function () end) self.rtu.connect_coil(function () self.turbine.decrementDumpingMode() end, function () end)
-- input registers -- -- input registers --
-- multiblock properties -- multiblock properties
@ -54,3 +56,5 @@ function new(turbine)
rtu_interface = rtu_interface rtu_interface = rtu_interface
} }
end end
return turbinev_rtu

View File

@ -1,10 +1,13 @@
-- #REQUIRES types.lua local comms = require("scada-common.comms")
local types = require("scada-common.types")
local modbus = {}
local MODBUS_FCODE = types.MODBUS_FCODE local MODBUS_FCODE = types.MODBUS_FCODE
local MODBUS_EXCODE = types.MODBUS_EXCODE local MODBUS_EXCODE = types.MODBUS_EXCODE
-- new modbus comms handler object -- new modbus comms handler object
function new(rtu_dev, use_parallel_read) modbus.new = function (rtu_dev, use_parallel_read)
local self = { local self = {
rtu = rtu_dev, rtu = rtu_dev,
use_parallel = use_parallel_read use_parallel = use_parallel_read
@ -401,3 +404,5 @@ function new(rtu_dev, use_parallel_read)
reply__gw_unavailable = reply__gw_unavailable reply__gw_unavailable = reply__gw_unavailable
} }
end end
return modbus

View File

@ -1,12 +1,15 @@
-- #REQUIRES comms.lua local comms = require("scada-common.comms")
-- #REQUIRES modbus.lua local ppm = require("scada-common.ppm")
-- #REQUIRES ppm.lua
local modbus = require("modbus")
local rtu = {}
local PROTOCOLS = comms.PROTOCOLS local PROTOCOLS = comms.PROTOCOLS
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES local RTU_ADVERT_TYPES = comms.RTU_ADVERT_TYPES
function rtu_init() rtu.init_unit = function ()
local self = { local self = {
discrete_inputs = {}, discrete_inputs = {},
coils = {}, coils = {},
@ -117,7 +120,7 @@ function rtu_init()
} }
end end
function rtu_comms(modem, local_port, server_port) rtu.comms = function (modem, local_port, server_port)
local self = { local self = {
seq_num = 0, seq_num = 0,
r_seq_num = nil, r_seq_num = nil,
@ -187,7 +190,7 @@ function rtu_comms(modem, local_port, server_port)
pkt = mgmt_pkt.get() pkt = mgmt_pkt.get()
end end
else else
log._error("illegal packet type " .. s_pkt.protocol(), true) log.error("illegal packet type " .. s_pkt.protocol(), true)
end end
end end
@ -203,7 +206,7 @@ function rtu_comms(modem, local_port, server_port)
if self.r_seq_num == nil then if self.r_seq_num == nil then
self.r_seq_num = packet.scada_frame.seq_num() self.r_seq_num = packet.scada_frame.seq_num()
elseif rtu_state.linked and self.r_seq_num >= packet.scada_frame.seq_num() then elseif rtu_state.linked and self.r_seq_num >= packet.scada_frame.seq_num() then
log._warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return return
else else
self.r_seq_num = packet.scada_frame.seq_num() self.r_seq_num = packet.scada_frame.seq_num()
@ -224,7 +227,7 @@ function rtu_comms(modem, local_port, server_port)
-- immediately execute redstone RTU requests -- immediately execute redstone RTU requests
local return_code, reply = unit.modbus_io.handle_packet(packet) local return_code, reply = unit.modbus_io.handle_packet(packet)
if not return_code then if not return_code then
log._warning("requested MODBUS operation failed") log.warning("requested MODBUS operation failed")
end end
else else
-- check validity then pass off to unit comms thread -- check validity then pass off to unit comms thread
@ -237,13 +240,13 @@ function rtu_comms(modem, local_port, server_port)
unit.pkt_queue.push(packet) unit.pkt_queue.push(packet)
end end
else else
log._warning("cannot perform requested MODBUS operation") log.warning("cannot perform requested MODBUS operation")
end end
end end
else else
-- unit ID out of range? -- unit ID out of range?
reply = modbus.reply__gw_unavailable(packet) reply = modbus.reply__gw_unavailable(packet)
log._error("MODBUS packet requesting non-existent unit") log.error("MODBUS packet requesting non-existent unit")
end end
send_modbus(reply) send_modbus(reply)
@ -253,7 +256,7 @@ function rtu_comms(modem, local_port, server_port)
-- close connection -- close connection
conn_watchdog.cancel() conn_watchdog.cancel()
unlink(rtu_state) unlink(rtu_state)
if packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then elseif packet.type == SCADA_MGMT_TYPES.REMOTE_LINKED then
-- acknowledgement -- acknowledgement
rtu_state.linked = true rtu_state.linked = true
self.r_seq_num = nil self.r_seq_num = nil
@ -262,11 +265,11 @@ function rtu_comms(modem, local_port, server_port)
send_advertisement(units) send_advertisement(units)
else else
-- not supported -- not supported
log._warning("RTU got unexpected SCADA message type " .. packet.type, true) log.warning("RTU got unexpected SCADA message type " .. packet.type, true)
end end
else else
-- should be unreachable assuming packet is from parse_packet() -- should be unreachable assuming packet is from parse_packet()
log._error("illegal packet type " .. protocol, true) log.error("illegal packet type " .. protocol, true)
end end
end end
end end
@ -337,3 +340,5 @@ function rtu_comms(modem, local_port, server_port)
close = close close = close
} }
end end
return rtu

View File

@ -2,28 +2,27 @@
-- RTU: Remote Terminal Unit -- RTU: Remote Terminal Unit
-- --
os.loadAPI("scada-common/log.lua") local log = require("scada-common.log")
os.loadAPI("scada-common/types.lua") local mqueue = require("scada-common.mqueue")
os.loadAPI("scada-common/util.lua") local ppm = require("scada-common.ppm")
os.loadAPI("scada-common/ppm.lua") local rsio = require("scada-common.rsio")
os.loadAPI("scada-common/comms.lua") local types = require("scada-common.types")
os.loadAPI("scada-common/mqueue.lua") local util = require("scada-common.util")
os.loadAPI("scada-common/rsio.lua")
os.loadAPI("config.lua") local config = require("config")
os.loadAPI("modbus.lua") local modbus = require("modbus")
os.loadAPI("rtu.lua") local rtu = require("rtu")
os.loadAPI("threads.lua") local threads = require("threads")
os.loadAPI("dev/redstone_rtu.lua") local redstone_rtu = require("dev.redstone_rtu")
os.loadAPI("dev/boiler_rtu.lua") local boiler_rtu = require("dev.boiler_rtu")
os.loadAPI("dev/boilerv_rtu.lua") local boilerv_rtu = require("dev.boilerv_rtu")
os.loadAPI("dev/energymachine_rtu.lua") local energymachine_rtu = require("dev.energymachine_rtu")
os.loadAPI("dev/imatrix_rtu.lua") local imatrix_rtu = require("dev.imatrix_rtu")
os.loadAPI("dev/turbine_rtu.lua") local turbine_rtu = require("dev.turbine_rtu")
os.loadAPI("dev/turbinev_rtu.lua") local turbinev_rtu = require("dev.turbinev_rtu")
local RTU_VERSION = "alpha-v0.5.0" local RTU_VERSION = "alpha-v0.6.0"
local rtu_t = types.rtu_t local rtu_t = types.rtu_t
@ -34,9 +33,9 @@ local println_ts = util.println_ts
log.init(config.LOG_PATH, config.LOG_MODE) log.init(config.LOG_PATH, config.LOG_MODE)
log._info("========================================") log.info("========================================")
log._info("BOOTING rtu.startup " .. RTU_VERSION) log.info("BOOTING rtu.startup " .. RTU_VERSION)
log._info("========================================") log.info("========================================")
println(">> RTU " .. RTU_VERSION .. " <<") println(">> RTU " .. RTU_VERSION .. " <<")
---------------------------------------- ----------------------------------------
@ -77,11 +76,11 @@ local smem_sys = __shared_memory.rtu_sys
-- get modem -- get modem
if smem_dev.modem == nil then if smem_dev.modem == nil then
println("boot> wireless modem not found") println("boot> wireless modem not found")
log._warning("no wireless modem on startup") log.warning("no wireless modem on startup")
return return
end end
smem_sys.rtu_comms = rtu.rtu_comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT) smem_sys.rtu_comms = rtu.comms(smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT)
---------------------------------------- ----------------------------------------
-- interpret config and init units -- interpret config and init units
@ -99,7 +98,7 @@ for reactor_idx = 1, #rtu_redstone do
local capabilities = {} local capabilities = {}
log._debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...") log.debug("init> starting redstone RTU I/O linking for reactor " .. rtu_redstone[reactor_idx].for_reactor .. "...")
for i = 1, #io_table do for i = 1, #io_table do
local valid = false local valid = false
@ -118,7 +117,7 @@ for reactor_idx = 1, #rtu_redstone do
local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. reactor_idx .. local message = "init> invalid redstone definition at index " .. i .. " in definition block #" .. reactor_idx ..
" (for reactor " .. rtu_redstone[reactor_idx].for_reactor .. ")" " (for reactor " .. rtu_redstone[reactor_idx].for_reactor .. ")"
println_ts(message) println_ts(message)
log._warning(message) log.warning(message)
else else
-- link redstone in RTU -- link redstone in RTU
local mode = rsio.get_io_mode(conf.channel) local mode = rsio.get_io_mode(conf.channel)
@ -132,13 +131,13 @@ for reactor_idx = 1, #rtu_redstone do
rs_rtu.link_ao(conf.channel, conf.side) rs_rtu.link_ao(conf.channel, conf.side)
else else
-- should be unreachable code, we already validated channels -- should be unreachable code, we already validated channels
log._error("init> fell through if chain attempting to identify IO mode", true) log.error("init> fell through if chain attempting to identify IO mode", true)
break break
end end
table.insert(capabilities, conf.channel) table.insert(capabilities, conf.channel)
log._debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side .. log.debug("init> linked redstone " .. #capabilities .. ": " .. rsio.to_string(conf.channel) .. " (" .. conf.side ..
") for reactor " .. rtu_redstone[reactor_idx].for_reactor) ") for reactor " .. rtu_redstone[reactor_idx].for_reactor)
end end
end end
@ -156,7 +155,7 @@ for reactor_idx = 1, #rtu_redstone do
thread = nil thread = nil
}) })
log._debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor) log.debug("init> initialized RTU unit #" .. #units .. ": redstone_io (redstone) [1] for reactor " .. rtu_redstone[reactor_idx].for_reactor)
end end
-- mounted peripherals -- mounted peripherals
@ -166,7 +165,7 @@ for i = 1, #rtu_devices do
if device == nil then if device == nil then
local message = "init> '" .. rtu_devices[i].name .. "' not found" local message = "init> '" .. rtu_devices[i].name .. "' not found"
println_ts(message) println_ts(message)
log._warning(message) log.warning(message)
else else
local type = ppm.get_type(rtu_devices[i].name) local type = ppm.get_type(rtu_devices[i].name)
local rtu_iface = nil local rtu_iface = nil
@ -200,7 +199,7 @@ for i = 1, #rtu_devices do
else else
local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")" local message = "init> device '" .. rtu_devices[i].name .. "' is not a known type (" .. type .. ")"
println_ts(message) println_ts(message)
log._warning(message) log.warning(message)
end end
if rtu_iface ~= nil then if rtu_iface ~= nil then
@ -221,7 +220,7 @@ for i = 1, #rtu_devices do
table.insert(units, rtu_unit) table.insert(units, rtu_unit)
log._debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" .. log.debug("init> initialized RTU unit #" .. #units .. ": " .. rtu_devices[i].name .. " (" .. rtu_type .. ") [" ..
rtu_devices[i].index .. "] for reactor " .. rtu_devices[i].for_reactor) rtu_devices[i].index .. "] for reactor " .. rtu_devices[i].for_reactor)
end end
end end
@ -237,7 +236,7 @@ local comms_thread = threads.thread__comms(__shared_memory)
-- start connection watchdog -- start connection watchdog
smem_sys.conn_watchdog = util.new_watchdog(5) smem_sys.conn_watchdog = util.new_watchdog(5)
log._debug("init> conn watchdog started") log.debug("init> conn watchdog started")
-- assemble thread list -- assemble thread list
local _threads = { main_thread.exec, comms_thread.exec } local _threads = { main_thread.exec, comms_thread.exec }
@ -251,4 +250,4 @@ end
parallel.waitForAll(table.unpack(_threads)) parallel.waitForAll(table.unpack(_threads))
println_ts("exited") println_ts("exited")
log._info("exited") log.info("exited")

View File

@ -1,7 +1,22 @@
-- #REQUIRES comms.lua local comms = require("scada-common.comms")
-- #REQUIRES log.lua local log = require("scada-common.log")
-- #REQUIRES ppm.lua local ppm = require("scada-common.ppm")
-- #REQUIRES util.lua local types = require("scada-common.types")
local util = require("scada-common.util")
local redstone_rtu = require("dev.redstone_rtu")
local boiler_rtu = require("dev.boiler_rtu")
local boilerv_rtu = require("dev.boilerv_rtu")
local energymachine_rtu = require("dev.energymachine_rtu")
local imatrix_rtu = require("dev.imatrix_rtu")
local turbine_rtu = require("dev.turbine_rtu")
local turbinev_rtu = require("dev.turbinev_rtu")
local modbus = require("modbus")
local threads = {}
local rtu_t = types.rtu_t
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -14,10 +29,10 @@ local MAIN_CLOCK = 2 -- (2Hz, 40 ticks)
local COMMS_SLEEP = 150 -- (150ms, 3 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks)
-- main thread -- main thread
function thread__main(smem) threads.thread__main = function (smem)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("main thread start") log.debug("main thread start")
-- advertisement/heartbeat clock -- advertisement/heartbeat clock
local loop_clock = os.startTimer(MAIN_CLOCK) local loop_clock = os.startTimer(MAIN_CLOCK)
@ -62,9 +77,9 @@ function thread__main(smem)
-- we only care if this is our wireless modem -- we only care if this is our wireless modem
if device.dev == rtu_dev.modem then if device.dev == rtu_dev.modem then
println_ts("wireless modem disconnected!") println_ts("wireless modem disconnected!")
log._warning("comms modem disconnected!") log.warning("comms modem disconnected!")
else else
log._warning("non-comms modem disconnected") log.warning("non-comms modem disconnected")
end end
else else
for i = 1, #units do for i = 1, #units do
@ -88,9 +103,9 @@ function thread__main(smem)
rtu_comms.reconnect_modem(rtu_dev.modem) rtu_comms.reconnect_modem(rtu_dev.modem)
println_ts("wireless modem reconnected.") println_ts("wireless modem reconnected.")
log._info("comms modem reconnected.") log.info("comms modem reconnected.")
else else
log._info("wired modem reconnected.") log.info("wired modem reconnected.")
end end
else else
-- relink lost peripheral to correct unit entry -- relink lost peripheral to correct unit entry
@ -102,11 +117,17 @@ function thread__main(smem)
-- found, re-link -- found, re-link
unit.device = device unit.device = device
if unit.type == "boiler" then if unit.type == rtu_t.boiler then
unit.rtu = boiler_rtu.new(device) unit.rtu = boiler_rtu.new(device)
elseif unit.type == "turbine" then 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) unit.rtu = turbine_rtu.new(device)
elseif unit.type == "imatrix" then 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) unit.rtu = imatrix_rtu.new(device)
end end
@ -121,7 +142,7 @@ function thread__main(smem)
-- check for termination request -- check for termination request
if event == "terminate" or ppm.should_terminate() then if event == "terminate" or ppm.should_terminate() then
rtu_state.shutdown = true rtu_state.shutdown = true
log._info("terminate requested, main thread exiting") log.info("terminate requested, main thread exiting")
break break
end end
end end
@ -131,10 +152,10 @@ function thread__main(smem)
end end
-- communications handler thread -- communications handler thread
function thread__comms(smem) threads.thread__comms = function (smem)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("comms thread start") log.debug("comms thread start")
-- load in from shared memory -- load in from shared memory
local rtu_state = smem.rtu_state local rtu_state = smem.rtu_state
@ -169,8 +190,8 @@ function thread__comms(smem)
-- check for termination request -- check for termination request
if rtu_state.shutdown then if rtu_state.shutdown then
rtu_comms.close() rtu_comms.close(rtu_state)
log._info("comms thread exiting") log.info("comms thread exiting")
break break
end end
@ -183,10 +204,10 @@ function thread__comms(smem)
end end
-- per-unit communications handler thread -- per-unit communications handler thread
function thread__unit_comms(smem, unit) threads.thread__unit_comms = function (smem, unit)
-- execute thread -- execute thread
local exec = function () local exec = function ()
log._debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")") log.debug("rtu unit thread start -> " .. unit.name .. "(" .. unit.type .. ")")
-- load in from shared memory -- load in from shared memory
local rtu_state = smem.rtu_state local rtu_state = smem.rtu_state
@ -219,7 +240,7 @@ function thread__unit_comms(smem, unit)
-- check for termination request -- check for termination request
if rtu_state.shutdown then if rtu_state.shutdown then
log._info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")") log.info("rtu unit thread exiting -> " .. unit.name .. "(" .. unit.type .. ")")
break break
end end
@ -230,3 +251,5 @@ function thread__unit_comms(smem, unit)
return { exec = exec } return { exec = exec }
end end
return threads

View File

@ -1,4 +1,6 @@
-- #REQUIRES util.lua local util = require("scada-common.util")
local alarm = {}
SEVERITY = { SEVERITY = {
INFO = 0, -- basic info message INFO = 0, -- basic info message
@ -9,7 +11,27 @@ SEVERITY = {
EMERGENCY = 5 -- critical safety alarm EMERGENCY = 5 -- critical safety alarm
} }
function scada_alarm(severity, device, message) alarm.SEVERITY = SEVERITY
alarm.severity_to_string = function (severity)
if severity == SEVERITY.INFO then
return "INFO"
elseif severity == SEVERITY.WARNING then
return "WARNING"
elseif severity == SEVERITY.ALERT then
return "ALERT"
elseif severity == SEVERITY.FACILITY then
return "FACILITY"
elseif severity == SEVERITY.SAFETY then
return "SAFETY"
elseif severity == SEVERITY.EMERGENCY then
return "EMERGENCY"
else
return "UNKNOWN"
end
end
alarm.scada_alarm = function (severity, device, message)
local self = { local self = {
time = util.time(), time = util.time(),
ts_string = os.date("[%H:%M:%S]"), ts_string = os.date("[%H:%M:%S]"),
@ -19,7 +41,7 @@ function scada_alarm(severity, device, message)
} }
local format = function () local format = function ()
return self.ts_string .. " [" .. severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device ") >> " .. self.message
end end
local properties = function () local properties = function ()
@ -37,20 +59,4 @@ function scada_alarm(severity, device, message)
} }
end end
function severity_to_string(severity) return alarm
if severity == SEVERITY.INFO then
return "INFO"
elseif severity == SEVERITY.WARNING then
return "WARNING"
elseif severity == SEVERITY.ALERT then
return "ALERT"
elseif severity == SEVERITY.FACILITY then
return "FACILITY"
elseif severity == SEVERITY.SAFETY then
return "SAFETY"
elseif severity == SEVERITY.EMERGENCY then
return "EMERGENCY"
else
return "UNKNOWN"
end
end

View File

@ -1,4 +1,10 @@
PROTOCOLS = { --
-- Communications
--
local comms = {}
local PROTOCOLS = {
MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol
RPLC = 1, -- reactor PLC protocol RPLC = 1, -- reactor PLC protocol
SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc
@ -6,7 +12,7 @@ PROTOCOLS = {
COORD_API = 4 -- data/control packets for pocket computers to/from coordinators COORD_API = 4 -- data/control packets for pocket computers to/from coordinators
} }
RPLC_TYPES = { local RPLC_TYPES = {
KEEP_ALIVE = 0, -- keep alive packets KEEP_ALIVE = 0, -- keep alive packets
LINK_REQ = 1, -- linking requests LINK_REQ = 1, -- linking requests
STATUS = 2, -- reactor/system status STATUS = 2, -- reactor/system status
@ -19,13 +25,13 @@ RPLC_TYPES = {
ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately)
} }
RPLC_LINKING = { local RPLC_LINKING = {
ALLOW = 0, -- link approved ALLOW = 0, -- link approved
DENY = 1, -- link denied DENY = 1, -- link denied
COLLISION = 2 -- link denied due to existing active link COLLISION = 2 -- link denied due to existing active link
} }
SCADA_MGMT_TYPES = { local SCADA_MGMT_TYPES = {
PING = 0, -- generic ping PING = 0, -- generic ping
CLOSE = 1, -- close a connection CLOSE = 1, -- close a connection
REMOTE_LINKED = 2, -- remote device linked REMOTE_LINKED = 2, -- remote device linked
@ -33,15 +39,21 @@ SCADA_MGMT_TYPES = {
RTU_HEARTBEAT = 4 -- RTU heartbeat RTU_HEARTBEAT = 4 -- RTU heartbeat
} }
RTU_ADVERT_TYPES = { local RTU_ADVERT_TYPES = {
BOILER = 0, -- boiler BOILER = 0, -- boiler
TURBINE = 1, -- turbine TURBINE = 1, -- turbine
IMATRIX = 2, -- induction matrix IMATRIX = 2, -- induction matrix
REDSTONE = 3 -- redstone I/O REDSTONE = 3 -- redstone I/O
} }
comms.PROTOCOLS = PROTOCOLS
comms.RPLC_TYPES = RPLC_TYPES
comms.RPLC_LINKING = RPLC_LINKING
comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES
comms.RTU_ADVERT_TYPES = RTU_ADVERT_TYPES
-- generic SCADA packet object -- generic SCADA packet object
function scada_packet() comms.scada_packet = function ()
local self = { local self = {
modem_msg_in = nil, modem_msg_in = nil,
valid = false, valid = false,
@ -124,7 +136,7 @@ end
-- MODBUS packet -- MODBUS packet
-- modeled after MODBUS TCP packet -- modeled after MODBUS TCP packet
function modbus_packet() comms.modbus_packet = function ()
local self = { local self = {
frame = nil, frame = nil,
raw = nil, raw = nil,
@ -165,11 +177,11 @@ function modbus_packet()
return size_ok return size_ok
else else
log._debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true) log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
return false return false
end end
else else
log._debug("nil frame encountered", true) log.debug("nil frame encountered", true)
return false return false
end end
end end
@ -201,7 +213,7 @@ function modbus_packet()
end end
-- reactor PLC packet -- reactor PLC packet
function rplc_packet() comms.rplc_packet = function ()
local self = { local self = {
frame = nil, frame = nil,
raw = nil, raw = nil,
@ -256,11 +268,11 @@ function rplc_packet()
return ok return ok
else else
log._debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true) log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
return false return false
end end
else else
log._debug("nil frame encountered", true) log.debug("nil frame encountered", true)
return false return false
end end
end end
@ -291,7 +303,7 @@ function rplc_packet()
end end
-- SCADA management packet -- SCADA management packet
function mgmt_packet() comms.mgmt_packet = function ()
local self = { local self = {
frame = nil, frame = nil,
raw = nil, raw = nil,
@ -339,11 +351,11 @@ function mgmt_packet()
return ok return ok
else else
log._debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true) log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
return false return false
end end
else else
log._debug("nil frame encountered", true) log.debug("nil frame encountered", true)
return false return false
end end
end end
@ -374,7 +386,7 @@ end
-- SCADA coordinator packet -- SCADA coordinator packet
-- @todo -- @todo
function coord_packet() comms.coord_packet = function ()
local self = { local self = {
frame = nil, frame = nil,
raw = nil, raw = nil,
@ -418,11 +430,11 @@ function coord_packet()
return ok return ok
else else
log._debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true) log.debug("attempted COORD_DATA parse of incorrect protocol " .. frame.protocol(), true)
return false return false
end end
else else
log._debug("nil frame encountered", true) log.debug("nil frame encountered", true)
return false return false
end end
end end
@ -453,7 +465,7 @@ end
-- coordinator API (CAPI) packet -- coordinator API (CAPI) packet
-- @todo -- @todo
function capi_packet() comms.capi_packet = function ()
local self = { local self = {
frame = nil, frame = nil,
raw = nil, raw = nil,
@ -497,11 +509,11 @@ function capi_packet()
return ok return ok
else else
log._debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true) log.debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true)
return false return false
end end
else else
log._debug("nil frame encountered", true) log.debug("nil frame encountered", true)
return false return false
end end
end end
@ -529,3 +541,5 @@ function capi_packet()
get = get get = get
} }
end end
return comms

View File

@ -2,14 +2,17 @@
-- File System Logger -- File System Logger
-- --
-- we use extra short abbreviations since computer craft screens are very small local log = {}
-- underscores are used since some of these names are used elsewhere (e.g. 'debug' is a lua table)
MODE = { -- we use extra short abbreviations since computer craft screens are very small
local MODE = {
APPEND = 0, APPEND = 0,
NEW = 1 NEW = 1
} }
log.MODE = MODE
local LOG_DEBUG = true local LOG_DEBUG = true
local log_path = "/log.txt" local log_path = "/log.txt"
@ -50,7 +53,7 @@ local _log = function (msg)
end end
end end
function init(path, write_mode) log.init = function (path, write_mode)
log_path = path log_path = path
mode = write_mode mode = write_mode
@ -61,7 +64,7 @@ function init(path, write_mode)
end end
end end
function _debug(msg, trace) log.debug = function (msg, trace)
if LOG_DEBUG then if LOG_DEBUG then
local dbg_info = "" local dbg_info = ""
@ -80,15 +83,15 @@ function _debug(msg, trace)
end end
end end
function _info(msg) log.info = function (msg)
_log("[INF] " .. msg) _log("[INF] " .. msg)
end end
function _warning(msg) log.warning = function (msg)
_log("[WRN] " .. msg) _log("[WRN] " .. msg)
end end
function _error(msg, trace) log.error = function (msg, trace)
local dbg_info = "" local dbg_info = ""
if trace then if trace then
@ -105,6 +108,8 @@ function _error(msg, trace)
_log("[ERR] " .. dbg_info .. msg) _log("[ERR] " .. dbg_info .. msg)
end end
function _fatal(msg) log.fatal = function (msg)
_log("[FTL] " .. msg) _log("[FTL] " .. msg)
end end
return log

View File

@ -2,13 +2,17 @@
-- Message Queue -- Message Queue
-- --
TYPE = { local mqueue = {}
local TYPE = {
COMMAND = 0, COMMAND = 0,
DATA = 1, DATA = 1,
PACKET = 2 PACKET = 2
} }
function new() mqueue.TYPE = TYPE
mqueue.new = function ()
local queue = {} local queue = {}
local length = function () local length = function ()
@ -57,3 +61,5 @@ function new()
pop = pop pop = pop
} }
end end
return mqueue

View File

@ -1,10 +1,14 @@
-- #REQUIRES log.lua local log = require("scada-common.log")
-- --
-- Protected Peripheral Manager -- Protected Peripheral Manager
-- --
ACCESS_FAULT = nil local ppm = {}
local ACCESS_FAULT = nil
ppm.ACCESS_FAULT = ACCESS_FAULT
---------------------------- ----------------------------
-- PRIVATE DATA/FUNCTIONS -- -- PRIVATE DATA/FUNCTIONS --
@ -46,7 +50,7 @@ local peri_init = function (iface)
_ppm_sys.faulted = true _ppm_sys.faulted = true
if not _ppm_sys.mute then if not _ppm_sys.mute then
log._error("PPM: protected " .. key .. "() -> " .. result) log.error("PPM: protected " .. key .. "() -> " .. result)
end end
if result == "Terminated" then if result == "Terminated" then
@ -88,48 +92,48 @@ end
-- REPORTING -- -- REPORTING --
-- silence error prints -- silence error prints
function disable_reporting() ppm.disable_reporting = function ()
_ppm_sys.mute = true _ppm_sys.mute = true
end end
-- allow error prints -- allow error prints
function enable_reporting() ppm.enable_reporting = function ()
_ppm_sys.mute = false _ppm_sys.mute = false
end end
-- FAULT MEMORY -- -- FAULT MEMORY --
-- enable automatically clearing fault flag -- enable automatically clearing fault flag
function enable_afc() ppm.enable_afc = function ()
_ppm_sys.auto_cf = true _ppm_sys.auto_cf = true
end end
-- disable automatically clearing fault flag -- disable automatically clearing fault flag
function disable_afc() ppm.disable_afc = function ()
_ppm_sys.auto_cf = false _ppm_sys.auto_cf = false
end end
-- check fault flag -- check fault flag
function is_faulted() ppm.is_faulted = function ()
return _ppm_sys.faulted return _ppm_sys.faulted
end end
-- clear fault flag -- clear fault flag
function clear_fault() ppm.clear_fault = function ()
_ppm_sys.faulted = false _ppm_sys.faulted = false
end end
-- TERMINATION -- -- TERMINATION --
-- if a caught error was a termination request -- if a caught error was a termination request
function should_terminate() ppm.should_terminate = function ()
return _ppm_sys.terminate return _ppm_sys.terminate
end end
-- MOUNTING -- -- MOUNTING --
-- mount all available peripherals (clears mounts first) -- mount all available peripherals (clears mounts first)
function mount_all() ppm.mount_all = function ()
local ifaces = peripheral.getNames() local ifaces = peripheral.getNames()
_ppm_sys.mounts = {} _ppm_sys.mounts = {}
@ -137,23 +141,23 @@ function mount_all()
for i = 1, #ifaces do for i = 1, #ifaces do
_ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i]) _ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i])
log._info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")") log.info("PPM: found a " .. _ppm_sys.mounts[ifaces[i]].type .. " (" .. ifaces[i] .. ")")
end end
if #ifaces == 0 then if #ifaces == 0 then
log._warning("PPM: mount_all() -> no devices found") log.warning("PPM: mount_all() -> no devices found")
end end
end end
-- mount a particular device -- mount a particular device
function mount(iface) ppm.mount = function (iface)
local ifaces = peripheral.getNames() local ifaces = peripheral.getNames()
local pm_dev = nil local pm_dev = nil
local pm_type = nil local pm_type = nil
for i = 1, #ifaces do for i = 1, #ifaces do
if iface == ifaces[i] then if iface == ifaces[i] then
log._info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface)) log.info("PPM: mount(" .. iface .. ") -> found a " .. peripheral.getType(iface))
_ppm_sys.mounts[iface] = peri_init(iface) _ppm_sys.mounts[iface] = peri_init(iface)
@ -167,15 +171,15 @@ function mount(iface)
end end
-- handle peripheral_detach event -- handle peripheral_detach event
function handle_unmount(iface) ppm.handle_unmount = function (iface)
-- what got disconnected? -- what got disconnected?
local lost_dev = _ppm_sys.mounts[iface] local lost_dev = _ppm_sys.mounts[iface]
if lost_dev then if lost_dev then
local type = lost_dev.type local type = lost_dev.type
log._warning("PPM: lost device " .. type .. " mounted to " .. iface) log.warning("PPM: lost device " .. type .. " mounted to " .. iface)
else else
log._error("PPM: lost device unknown to the PPM mounted to " .. iface) log.error("PPM: lost device unknown to the PPM mounted to " .. iface)
end end
return lost_dev return lost_dev
@ -184,31 +188,31 @@ end
-- GENERAL ACCESSORS -- -- GENERAL ACCESSORS --
-- list all available peripherals -- list all available peripherals
function list_avail() ppm.list_avail = function ()
return peripheral.getNames() return peripheral.getNames()
end end
-- list mounted peripherals -- list mounted peripherals
function list_mounts() ppm.list_mounts = function ()
return _ppm_sys.mounts return _ppm_sys.mounts
end end
-- get a mounted peripheral by side/interface -- get a mounted peripheral by side/interface
function get_periph(iface) ppm.get_periph = function (iface)
if _ppm_sys.mounts[iface] then if _ppm_sys.mounts[iface] then
return _ppm_sys.mounts[iface].dev return _ppm_sys.mounts[iface].dev
else return nil end else return nil end
end end
-- get a mounted peripheral type by side/interface -- get a mounted peripheral type by side/interface
function get_type(iface) ppm.get_type = function (iface)
if _ppm_sys.mounts[iface] then if _ppm_sys.mounts[iface] then
return _ppm_sys.mounts[iface].type return _ppm_sys.mounts[iface].type
else return nil end else return nil end
end end
-- get all mounted peripherals by type -- get all mounted peripherals by type
function get_all_devices(name) ppm.get_all_devices = function (name)
local devices = {} local devices = {}
for side, data in pairs(_ppm_sys.mounts) do for side, data in pairs(_ppm_sys.mounts) do
@ -221,7 +225,7 @@ function get_all_devices(name)
end end
-- get a mounted peripheral by type (if multiple, returns the first) -- get a mounted peripheral by type (if multiple, returns the first)
function get_device(name) ppm.get_device = function (name)
local device = nil local device = nil
for side, data in pairs(_ppm_sys.mounts) do for side, data in pairs(_ppm_sys.mounts) do
@ -237,12 +241,12 @@ end
-- SPECIFIC DEVICE ACCESSORS -- -- SPECIFIC DEVICE ACCESSORS --
-- get the fission reactor (if multiple, returns the first) -- get the fission reactor (if multiple, returns the first)
function get_fission_reactor() ppm.get_fission_reactor = function ()
return get_device("fissionReactor") return ppm.get_device("fissionReactor")
end end
-- get the wireless modem (if multiple, returns the first) -- get the wireless modem (if multiple, returns the first)
function get_wireless_modem() ppm.get_wireless_modem = function ()
local w_modem = nil local w_modem = nil
for side, device in pairs(_ppm_sys.mounts) do for side, device in pairs(_ppm_sys.mounts) do
@ -256,6 +260,8 @@ function get_wireless_modem()
end end
-- list all connected monitors -- list all connected monitors
function list_monitors() ppm.list_monitors = function ()
return get_all_devices("monitor") return ppm.get_all_devices("monitor")
end end
return ppm

View File

@ -1,21 +1,27 @@
IO_LVL = { --
-- Redstone I/O
--
local rsio = {}
local IO_LVL = {
LOW = 0, LOW = 0,
HIGH = 1 HIGH = 1
} }
IO_DIR = { local IO_DIR = {
IN = 0, IN = 0,
OUT = 1 OUT = 1
} }
IO_MODE = { local IO_MODE = {
DIGITAL_OUT = 0, DIGITAL_OUT = 0,
DIGITAL_IN = 1, DIGITAL_IN = 1,
ANALOG_OUT = 2, ANALOG_OUT = 2,
ANALOG_IN = 3 ANALOG_IN = 3
} }
RS_IO = { local RS_IO = {
-- digital inputs -- -- digital inputs --
-- facility -- facility
@ -53,7 +59,12 @@ RS_IO = {
A_T_FLOW_RATE = 21 -- turbine flow rate percentage A_T_FLOW_RATE = 21 -- turbine flow rate percentage
} }
function to_string(channel) rsio.IO_LVL = IO_LVL
rsio.IO_DIR = IO_DIR
rsio.IO_MODE = IO_MODE
rsio.IO = RS_IO
rsio.to_string = function (channel)
local names = { local names = {
"F_SCRAM", "F_SCRAM",
"F_AE2_LIVE", "F_AE2_LIVE",
@ -85,11 +96,11 @@ function to_string(channel)
end end
end end
function is_valid_channel(channel) rsio.is_valid_channel = function (channel)
return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE return channel ~= nil and channel > 0 and channel <= RS_IO.A_T_FLOW_RATE
end end
function is_valid_side(side) rsio.is_valid_side = function (side)
if side ~= nil then if side ~= nil then
for _, s in pairs(rs.getSides()) do for _, s in pairs(rs.getSides()) do
if s == side then return true end if s == side then return true end
@ -98,7 +109,7 @@ function is_valid_side(side)
return false return false
end end
function is_color(color) rsio.is_color = function (color)
return (color > 0) and (bit.band(color, (color - 1)) == 0); return (color > 0) and (bit.band(color, (color - 1)) == 0);
end end
@ -149,7 +160,7 @@ local RS_DIO_MAP = {
{ _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT } { _f = _DO_ACTIVE_HIGH, mode = IO_DIR.OUT }
} }
function get_io_mode(channel) rsio.get_io_mode = function (channel)
local modes = { local modes = {
IO_MODE.DIGITAL_IN, -- F_SCRAM IO_MODE.DIGITAL_IN, -- F_SCRAM
IO_MODE.DIGITAL_IN, -- F_AE2_LIVE IO_MODE.DIGITAL_IN, -- F_AE2_LIVE
@ -182,7 +193,7 @@ function get_io_mode(channel)
end end
-- get digital IO level reading -- get digital IO level reading
function digital_read(rs_value) rsio.digital_read = function (rs_value)
if rs_value then if rs_value then
return IO_LVL.HIGH return IO_LVL.HIGH
else else
@ -191,7 +202,7 @@ function digital_read(rs_value)
end end
-- returns the level corresponding to active -- returns the level corresponding to active
function digital_write(channel, active) rsio.digital_write = function (channel, active)
if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then if channel < RS_IO.WASTE_PO or channel > RS_IO.R_PLC_TIMEOUT then
return IO_LVL.LOW return IO_LVL.LOW
else else
@ -200,10 +211,12 @@ function digital_write(channel, active)
end end
-- returns true if the level corresponds to active -- returns true if the level corresponds to active
function digital_is_active(channel, level) rsio.digital_is_active = function (channel, level)
if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then if channel > RS_IO.R_ENABLE or channel > RS_IO.R_PLC_TIMEOUT then
return false return false
else else
return RS_DIO_MAP[channel]._f(level) return RS_DIO_MAP[channel]._f(level)
end end
end end
return rsio

View File

@ -1,6 +1,10 @@
--
-- Global Types -- Global Types
--
rtu_t = { local types = {}
types.rtu_t = {
redstone = "redstone", redstone = "redstone",
boiler = "boiler", boiler = "boiler",
boiler_valve = "boiler_valve", boiler_valve = "boiler_valve",
@ -10,7 +14,7 @@ rtu_t = {
induction_matrix = "induction_matrix" induction_matrix = "induction_matrix"
} }
iss_status_t = { types.iss_status_t = {
ok = "ok", ok = "ok",
dmg_crit = "dmg_crit", dmg_crit = "dmg_crit",
ex_hcoolant = "heated_coolant_backup", ex_hcoolant = "heated_coolant_backup",
@ -24,7 +28,7 @@ iss_status_t = {
-- MODBUS -- MODBUS
-- modbus function codes -- modbus function codes
local MODBUS_FCODE = { types.MODBUS_FCODE = {
READ_COILS = 0x01, READ_COILS = 0x01,
READ_DISCRETE_INPUTS = 0x02, READ_DISCRETE_INPUTS = 0x02,
READ_MUL_HOLD_REGS = 0x03, READ_MUL_HOLD_REGS = 0x03,
@ -37,7 +41,7 @@ local MODBUS_FCODE = {
} }
-- modbus exception codes -- modbus exception codes
local MODBUS_EXCODE = { types.MODBUS_EXCODE = {
ILLEGAL_FUNCTION = 0x01, ILLEGAL_FUNCTION = 0x01,
ILLEGAL_DATA_ADDR = 0x02, ILLEGAL_DATA_ADDR = 0x02,
ILLEGAL_DATA_VALUE = 0x03, ILLEGAL_DATA_VALUE = 0x03,
@ -49,3 +53,5 @@ local MODBUS_EXCODE = {
GATEWAY_PATH_UNAVAILABLE = 0x0A, GATEWAY_PATH_UNAVAILABLE = 0x0A,
GATEWAY_TARGET_TIMEOUT = 0x0B GATEWAY_TARGET_TIMEOUT = 0x0B
} }
return types

View File

@ -1,70 +1,69 @@
local util = {}
-- PRINT -- -- PRINT --
-- we are overwriting 'print' so save it first
local _print = print
-- print -- print
function print(message) util.print = function (message)
term.write(message) term.write(message)
end end
-- print line -- print line
function println(message) util.println = function (message)
_print(message) print(message)
end end
-- timestamped print -- timestamped print
function print_ts(message) util.print_ts = function (message)
term.write(os.date("[%H:%M:%S] ") .. message) term.write(os.date("[%H:%M:%S] ") .. message)
end end
-- timestamped print line -- timestamped print line
function println_ts(message) util.println_ts = function (message)
_print(os.date("[%H:%M:%S] ") .. message) print(os.date("[%H:%M:%S] ") .. message)
end end
-- TIME -- -- TIME --
function time_ms() util.time_ms = function ()
return os.epoch('local') return os.epoch('local')
end end
function time_s() util.time_s = function ()
return os.epoch('local') / 1000 return os.epoch('local') / 1000
end end
function time() util.time = function ()
return time_ms() return util.time_ms()
end end
-- PARALLELIZATION -- -- PARALLELIZATION --
-- protected sleep call so we still are in charge of catching termination -- protected sleep call so we still are in charge of catching termination
function psleep(t) util.psleep = function (t)
pcall(os.sleep, t) pcall(os.sleep, t)
end end
-- no-op to provide a brief pause (and a yield) -- no-op to provide a brief pause (and a yield)
-- EVENT_CONSUMER: this function consumes events -- EVENT_CONSUMER: this function consumes events
function nop() util.nop = function ()
psleep(0.05) util.psleep(0.05)
end end
-- attempt to maintain a minimum loop timing (duration of execution) -- attempt to maintain a minimum loop timing (duration of execution)
function adaptive_delay(target_timing, last_update) util.adaptive_delay = function (target_timing, last_update)
local sleep_for = target_timing - (time() - last_update) local sleep_for = target_timing - (util.time() - last_update)
-- only if >50ms since worker loops already yield 0.05s -- only if >50ms since worker loops already yield 0.05s
if sleep_for >= 50 then if sleep_for >= 50 then
psleep(sleep_for / 1000.0) util.psleep(sleep_for / 1000.0)
end end
return time() return util.time()
end end
-- WATCHDOG -- -- WATCHDOG --
-- ComputerCraft OS Timer based Watchdog -- ComputerCraft OS Timer based Watchdog
-- triggers a timer event if not fed within 'timeout' seconds -- triggers a timer event if not fed within 'timeout' seconds
function new_watchdog(timeout) util.new_watchdog = function (timeout)
local self = { local self = {
_timeout = timeout, _timeout = timeout,
_wd_timer = os.startTimer(timeout) _wd_timer = os.startTimer(timeout)
@ -93,3 +92,5 @@ function new_watchdog(timeout)
cancel = cancel cancel = cancel
} }
end end
return util

View File

@ -1,12 +1,16 @@
local config = {}
-- scada network listen for PLC's and RTU's -- scada network listen for PLC's and RTU's
SCADA_DEV_LISTEN = 16000 config.SCADA_DEV_LISTEN = 16000
-- listen port for SCADA supervisor access by coordinators -- listen port for SCADA supervisor access by coordinators
SCADA_SV_LISTEN = 16100 config.SCADA_SV_LISTEN = 16100
-- expected number of reactors -- expected number of reactors
NUM_REACTORS = 4 config.NUM_REACTORS = 4
-- log path -- log path
LOG_PATH = "/log.txt" config.LOG_PATH = "/log.txt"
-- log mode -- log mode
-- 0 = APPEND (adds to existing file on start) -- 0 = APPEND (adds to existing file on start)
-- 1 = NEW (replaces existing file on start) -- 1 = NEW (replaces existing file on start)
LOG_MODE = 0 config.LOG_MODE = 0
return config

View File

@ -0,0 +1,3 @@
local coordinator = {}
return coordinator

View File

@ -1,7 +1,9 @@
-- #REQUIRES mqueue.lua local comms = require("scada-common.comms")
-- #REQUIRES comms.lua local log = require("scada-common.log")
-- #REQUIRES log.lua local mqueue = require("scada-common.mqueue")
-- #REQUIRES util.lua local util = require("scada-common.util")
local plc = {}
local PROTOCOLS = comms.PROTOCOLS local PROTOCOLS = comms.PROTOCOLS
local RPLC_TYPES = comms.RPLC_TYPES local RPLC_TYPES = comms.RPLC_TYPES
@ -16,19 +18,21 @@ local println_ts = util.println_ts
local INITIAL_WAIT = 1500 local INITIAL_WAIT = 1500
local RETRY_PERIOD = 1000 local RETRY_PERIOD = 1000
PLC_S_CMDS = { local PLC_S_CMDS = {
SCRAM = 0, SCRAM = 0,
ENABLE = 1, ENABLE = 1,
BURN_RATE = 2, BURN_RATE = 2,
ISS_CLEAR = 3 ISS_CLEAR = 3
} }
plc.PLC_S_CMDS = PLC_S_CMDS
local PERIODICS = { local PERIODICS = {
KEEP_ALIVE = 2.0 KEEP_ALIVE = 2.0
} }
-- PLC supervisor session -- PLC supervisor session
function new_session(id, for_reactor, in_queue, out_queue) plc.new_session = function (id, for_reactor, in_queue, out_queue)
local log_header = "plc_session(" .. id .. "): " local log_header = "plc_session(" .. id .. "): "
local self = { local self = {
@ -204,7 +208,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
if pkt.length == 1 then if pkt.length == 1 then
return pkt.data[1] return pkt.data[1]
else else
log._warning(log_header .. "RPLC ACK length mismatch") log.warning(log_header .. "RPLC ACK length mismatch")
return nil return nil
end end
end end
@ -215,7 +219,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
if self.r_seq_num == nil then if self.r_seq_num == nil then
self.r_seq_num = pkt.scada_frame.seq_num() self.r_seq_num = pkt.scada_frame.seq_num()
elseif self.r_seq_num >= pkt.scada_frame.seq_num() then elseif self.r_seq_num >= pkt.scada_frame.seq_num() then
log._warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num())
return return
else else
self.r_seq_num = pkt.scada_frame.seq_num() self.r_seq_num = pkt.scada_frame.seq_num()
@ -225,7 +229,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
if pkt.scada_frame.protocol() == PROTOCOLS.RPLC then if pkt.scada_frame.protocol() == PROTOCOLS.RPLC then
-- check reactor ID -- check reactor ID
if pkt.id ~= for_reactor then if pkt.id ~= for_reactor then
log._warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id) log.warning(log_header .. "RPLC packet with ID not matching reactor ID: reactor " .. self.for_reactor .. " != " .. pkt.id)
return return
end end
@ -242,13 +246,13 @@ function new_session(id, for_reactor, in_queue, out_queue)
self.last_rtt = srv_now - srv_start self.last_rtt = srv_now - srv_start
if self.last_rtt > 500 then if self.last_rtt > 500 then
log._warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")") log.warning(log_header .. "PLC KEEP_ALIVE round trip time > 500ms (" .. self.last_rtt .. ")")
end end
-- log._debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms") -- log.debug(log_header .. "RPLC RTT = ".. self.last_rtt .. "ms")
-- log._debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms") -- log.debug(log_header .. "RPLC TT = ".. (srv_now - plc_send) .. "ms")
else else
log._debug(log_header .. "RPLC keep alive packet length mismatch") log.debug(log_header .. "RPLC keep alive packet length mismatch")
end end
elseif pkt.type == RPLC_TYPES.STATUS then elseif pkt.type == RPLC_TYPES.STATUS then
-- status packet received, update data -- status packet received, update data
@ -267,11 +271,11 @@ function new_session(id, for_reactor, in_queue, out_queue)
self.received_status_cache = true self.received_status_cache = true
else else
-- error copying status data -- error copying status data
log._error(log_header .. "failed to parse status packet data") log.error(log_header .. "failed to parse status packet data")
end end
end end
else else
log._debug(log_header .. "RPLC status packet length mismatch") log.debug(log_header .. "RPLC status packet length mismatch")
end end
elseif pkt.type == RPLC_TYPES.MEK_STRUCT then elseif pkt.type == RPLC_TYPES.MEK_STRUCT then
-- received reactor structure, record it -- received reactor structure, record it
@ -282,10 +286,10 @@ function new_session(id, for_reactor, in_queue, out_queue)
self.received_struct = true self.received_struct = true
else else
-- error copying structure data -- error copying structure data
log._error(log_header .. "failed to parse struct packet data") log.error(log_header .. "failed to parse struct packet data")
end end
else else
log._debug(log_header .. "RPLC struct packet length mismatch") log.debug(log_header .. "RPLC struct packet length mismatch")
end end
elseif pkt.type == RPLC_TYPES.MEK_SCRAM then elseif pkt.type == RPLC_TYPES.MEK_SCRAM then
-- SCRAM acknowledgement -- SCRAM acknowledgement
@ -294,7 +298,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
self.acks.scram = true self.acks.scram = true
self.sDB.control_state = false self.sDB.control_state = false
elseif ack == false then elseif ack == false then
log._debug(log_header .. "SCRAM failed!") log.debug(log_header .. "SCRAM failed!")
end end
elseif pkt.type == RPLC_TYPES.MEK_ENABLE then elseif pkt.type == RPLC_TYPES.MEK_ENABLE then
-- enable acknowledgement -- enable acknowledgement
@ -303,7 +307,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
self.acks.enable = true self.acks.enable = true
self.sDB.control_state = true self.sDB.control_state = true
elseif ack == false then elseif ack == false then
log._debug(log_header .. "enable failed!") log.debug(log_header .. "enable failed!")
end end
elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then elseif pkt.type == RPLC_TYPES.MEK_BURN_RATE then
-- burn rate acknowledgement -- burn rate acknowledgement
@ -311,7 +315,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
if ack then if ack then
self.acks.burn_rate = true self.acks.burn_rate = true
elseif ack == false then elseif ack == false then
log._debug(log_header .. "burn rate update failed!") log.debug(log_header .. "burn rate update failed!")
end end
elseif pkt.type == RPLC_TYPES.ISS_STATUS then elseif pkt.type == RPLC_TYPES.ISS_STATUS then
-- ISS status packet received, copy data -- ISS status packet received, copy data
@ -321,10 +325,10 @@ function new_session(id, for_reactor, in_queue, out_queue)
-- copied in ISS status data OK -- copied in ISS status data OK
else else
-- error copying ISS status data -- error copying ISS status data
log._error(log_header .. "failed to parse ISS status packet data") log.error(log_header .. "failed to parse ISS status packet data")
end end
else else
log._debug(log_header .. "RPLC ISS status packet length mismatch") log.debug(log_header .. "RPLC ISS status packet length mismatch")
end end
elseif pkt.type == RPLC_TYPES.ISS_ALARM then elseif pkt.type == RPLC_TYPES.ISS_ALARM then
-- ISS alarm -- ISS alarm
@ -337,10 +341,10 @@ function new_session(id, for_reactor, in_queue, out_queue)
-- copied in ISS status data OK -- copied in ISS status data OK
else else
-- error copying ISS status data -- error copying ISS status data
log._error(log_header .. "failed to parse ISS alarm status data") log.error(log_header .. "failed to parse ISS alarm status data")
end end
else else
log._debug(log_header .. "RPLC ISS alarm packet length mismatch") log.debug(log_header .. "RPLC ISS alarm packet length mismatch")
end end
elseif pkt.type == RPLC_TYPES.ISS_CLEAR then elseif pkt.type == RPLC_TYPES.ISS_CLEAR then
-- ISS clear acknowledgement -- ISS clear acknowledgement
@ -350,17 +354,17 @@ function new_session(id, for_reactor, in_queue, out_queue)
self.sDB.iss_tripped = false self.sDB.iss_tripped = false
self.sDB.iss_trip_cause = "ok" self.sDB.iss_trip_cause = "ok"
elseif ack == false then elseif ack == false then
log._debug(log_header .. "ISS clear failed") log.debug(log_header .. "ISS clear failed")
end end
else else
log._debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type) log.debug(log_header .. "handler received unsupported RPLC packet type " .. pkt.type)
end end
elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then elseif pkt.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then
if pkt.type == SCADA_MGMT_TYPES.CLOSE then if pkt.type == SCADA_MGMT_TYPES.CLOSE then
-- close the session -- close the session
self.connected = false self.connected = false
else else
log._debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type)
end end
end end
end end
@ -402,7 +406,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
self.connected = false self.connected = false
_send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {})
println("connection to reactor " .. self.for_reactor .. " PLC closed by server") println("connection to reactor " .. self.for_reactor .. " PLC closed by server")
log._info(log_header .. "session closed by server") log.info(log_header .. "session closed by server")
end end
-- iterate the session -- iterate the session
@ -454,7 +458,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
-- max 100ms spent processing queue -- max 100ms spent processing queue
if util.time() - handle_start > 100 then if util.time() - handle_start > 100 then
log._warning(log_header .. "exceeded 100ms queue process limit") log.warning(log_header .. "exceeded 100ms queue process limit")
break break
end end
end end
@ -463,7 +467,7 @@ function new_session(id, for_reactor, in_queue, out_queue)
if not self.connected then if not self.connected then
self.plc_conn_watchdog.cancel() self.plc_conn_watchdog.cancel()
println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host") println("connection to reactor " .. self.for_reactor .. " PLC closed by remote host")
log._info(log_header .. "session closed by remote host") log.info(log_header .. "session closed by remote host")
return self.connected return self.connected
end end
@ -559,3 +563,5 @@ function new_session(id, for_reactor, in_queue, out_queue)
iterate = iterate iterate = iterate
} }
end end
return plc

View File

@ -0,0 +1,3 @@
local rtu = {}
return rtu

View File

@ -1,14 +1,22 @@
-- #REQUIRES mqueue.lua local log = require("scada-common.log")
-- #REQUIRES log.lua local mqueue = require("scada-common.mqueue")
local coordinator = require("session.coordinator")
local plc = require("session.plc")
local rtu = require("session.rtu")
-- Supervisor Sessions Handler -- Supervisor Sessions Handler
SESSION_TYPE = { local svsessions = {}
local SESSION_TYPE = {
RTU_SESSION = 0, RTU_SESSION = 0,
PLC_SESSION = 1, PLC_SESSION = 1,
COORD_SESSION = 2 COORD_SESSION = 2
} }
svsessions.SESSION_TYPE = SESSION_TYPE
local self = { local self = {
modem = nil, modem = nil,
num_reactors = 0, num_reactors = 0,
@ -20,12 +28,97 @@ local self = {
next_coord_id = 0 next_coord_id = 0
} }
function link_modem(modem) -- PRIVATE FUNCTIONS --
-- iterate all the given sessions
local function _iterate(sessions)
for i = 1, #sessions do
local session = sessions[i]
if session.open then
local ok = session.instance.iterate()
if ok then
-- send packets in out queue
while session.out_queue.ready() do
local msg = session.out_queue.pop()
if msg.qtype == mqueue.TYPE.PACKET then
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
end
end
else
session.open = false
end
end
end
end
-- cleanly close a session
local function _shutdown(session)
session.open = false
session.instance.close()
-- send packets in out queue (namely the close packet)
while session.out_queue.ready() do
local msg = session.out_queue.pop()
if msg.qtype == mqueue.TYPE.PACKET then
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
end
end
log.debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port)
end
-- close connections
local function _close(sessions)
for i = 1, #sessions do
local session = sessions[i]
if session.open then
_shutdown(session)
end
end
end
-- check if a watchdog timer event matches that of one of the provided sessions
local function _check_watchdogs(sessions, timer_event)
for i = 1, #sessions do
local session = sessions[i]
if session.open then
local triggered = session.instance.check_wd(timer_event)
if triggered then
log.debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...")
_shutdown(session)
end
end
end
end
-- delete any closed sessions
local function _free_closed(sessions)
local move_to = 1
for i = 1, #sessions do
local session = sessions[i]
if session ~= nil then
if sessions[i].open then
if sessions[move_to] == nil then
sessions[move_to] = session
sessions[i] = nil
end
move_to = move_to + 1
else
log.debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port)
sessions[i] = nil
end
end
end
end
-- PUBLIC FUNCTIONS --
svsessions.link_modem = function (modem)
self.modem = modem self.modem = modem
end end
-- find a session by the remote port -- find a session by the remote port
function find_session(remote_port) svsessions.find_session = function (remote_port)
-- check RTU sessions -- check RTU sessions
for i = 1, #self.rtu_sessions do for i = 1, #self.rtu_sessions do
if self.rtu_sessions[i].r_port == remote_port then if self.rtu_sessions[i].r_port == remote_port then
@ -51,7 +144,7 @@ function find_session(remote_port)
end end
-- get a session by reactor ID -- get a session by reactor ID
function get_reactor_session(reactor) svsessions.get_reactor_session = function (reactor)
local session = nil local session = nil
for i = 1, #self.plc_sessions do for i = 1, #self.plc_sessions do
@ -64,8 +157,8 @@ function get_reactor_session(reactor)
end end
-- establish a new PLC session -- establish a new PLC session
function establish_plc_session(local_port, remote_port, for_reactor) svsessions.establish_plc_session = function (local_port, remote_port, for_reactor)
if get_reactor_session(for_reactor) == nil then if svsessions.get_reactor_session(for_reactor) == nil then
local plc_s = { local plc_s = {
open = true, open = true,
reactor = for_reactor, reactor = for_reactor,
@ -79,7 +172,7 @@ function establish_plc_session(local_port, remote_port, for_reactor)
plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue) plc_s.instance = plc.new_session(self.next_plc_id, for_reactor, plc_s.in_queue, plc_s.out_queue)
table.insert(self.plc_sessions, plc_s) table.insert(self.plc_sessions, plc_s)
log._debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id) log.debug("established new PLC session to " .. remote_port .. " with ID " .. self.next_plc_id)
self.next_plc_id = self.next_plc_id + 1 self.next_plc_id = self.next_plc_id + 1
@ -91,38 +184,8 @@ function establish_plc_session(local_port, remote_port, for_reactor)
end end
end end
-- cleanly close a session
local function _shutdown(session)
session.open = false
session.instance.close()
-- send packets in out queue (namely the close packet)
while session.out_queue.ready() do
local msg = session.out_queue.pop()
if msg.qtype == mqueue.TYPE.PACKET then
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
end
end
log._debug("closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port)
end
-- check if a watchdog timer event matches that of one of the provided sessions
local function _check_watchdogs(sessions, timer_event)
for i = 1, #sessions do
local session = sessions[i]
if session.open then
local triggered = session.instance.check_wd(timer_event)
if triggered then
log._debug("watchdog closing session " .. session.instance.get_id() .. " on remote port " .. session.r_port .. "...")
_shutdown(session)
end
end
end
end
-- attempt to identify which session's watchdog timer fired -- attempt to identify which session's watchdog timer fired
function check_all_watchdogs(timer_event) svsessions.check_all_watchdogs = function (timer_event)
-- check RTU session watchdogs -- check RTU session watchdogs
_check_watchdogs(self.rtu_sessions, timer_event) _check_watchdogs(self.rtu_sessions, timer_event)
@ -133,29 +196,8 @@ function check_all_watchdogs(timer_event)
_check_watchdogs(self.coord_sessions, timer_event) _check_watchdogs(self.coord_sessions, timer_event)
end end
-- iterate all the given sessions
local function _iterate(sessions)
for i = 1, #sessions do
local session = sessions[i]
if session.open then
local ok = session.instance.iterate()
if ok then
-- send packets in out queue
while session.out_queue.ready() do
local msg = session.out_queue.pop()
if msg.qtype == mqueue.TYPE.PACKET then
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
end
end
else
session.open = false
end
end
end
end
-- iterate all sessions -- iterate all sessions
function iterate_all() svsessions.iterate_all = function ()
-- iterate RTU sessions -- iterate RTU sessions
_iterate(self.rtu_sessions) _iterate(self.rtu_sessions)
@ -166,28 +208,8 @@ function iterate_all()
_iterate(self.coord_sessions) _iterate(self.coord_sessions)
end end
-- delete any closed sessions
local function _free_closed(sessions)
local move_to = 1
for i = 1, #sessions do
local session = sessions[i]
if session ~= nil then
if sessions[i].open then
if sessions[move_to] == nil then
sessions[move_to] = session
sessions[i] = nil
end
move_to = move_to + 1
else
log._debug("free'ing closed session " .. session.instance.get_id() .. " on remote port " .. session.r_port)
sessions[i] = nil
end
end
end
end
-- delete all closed sessions -- delete all closed sessions
function free_all_closed() svsessions.free_all_closed = function ()
-- free closed RTU sessions -- free closed RTU sessions
_free_closed(self.rtu_sessions) _free_closed(self.rtu_sessions)
@ -198,23 +220,15 @@ function free_all_closed()
_free_closed(self.coord_sessions) _free_closed(self.coord_sessions)
end end
-- close connections
local function _close(sessions)
for i = 1, #sessions do
local session = sessions[i]
if session.open then
_shutdown(session)
end
end
end
-- close all open connections -- close all open connections
function close_all() svsessions.close_all = function ()
-- close sessions -- close sessions
_close(self.rtu_sessions) _close(self.rtu_sessions)
_close(self.plc_sessions) _close(self.plc_sessions)
_close(self.coord_sessions) _close(self.coord_sessions)
-- free sessions -- free sessions
free_all_closed() svsessions.free_all_closed()
end end
return svsessions

View File

@ -2,23 +2,19 @@
-- Nuclear Generation Facility SCADA Supervisor -- Nuclear Generation Facility SCADA Supervisor
-- --
os.loadAPI("scada-common/log.lua") local log = require("scada-common.log")
os.loadAPI("scada-common/types.lua") local ppm = require("scada-common.ppm")
os.loadAPI("scada-common/util.lua") local util = require("scada-common.util")
os.loadAPI("scada-common/ppm.lua")
os.loadAPI("scada-common/comms.lua")
os.loadAPI("scada-common/mqueue.lua")
os.loadAPI("config.lua") local coordinator = require("session.coordinator")
local plc = require("session.plc")
local rtu = require("session.rtu")
local svsessions = require("session.svsessions")
os.loadAPI("session/rtu.lua") local config = require("config")
os.loadAPI("session/plc.lua") local supervisor = require("supervisor")
os.loadAPI("session/coordinator.lua")
os.loadAPI("session/svsessions.lua")
os.loadAPI("supervisor.lua") local SUPERVISOR_VERSION = "alpha-v0.3.0"
local SUPERVISOR_VERSION = "alpha-v0.2.0"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -27,9 +23,9 @@ local println_ts = util.println_ts
log.init(config.LOG_PATH, config.LOG_MODE) log.init(config.LOG_PATH, config.LOG_MODE)
log._info("========================================") log.info("========================================")
log._info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION)
log._info("========================================") log.info("========================================")
println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<") println(">> SCADA Supervisor " .. SUPERVISOR_VERSION .. " <<")
-- mount connected devices -- mount connected devices
@ -38,12 +34,12 @@ ppm.mount_all()
local modem = ppm.get_wireless_modem() local modem = ppm.get_wireless_modem()
if modem == nil then if modem == nil then
println("boot> wireless modem not found") println("boot> wireless modem not found")
log._warning("no wireless modem on startup") log.warning("no wireless modem on startup")
return return
end end
-- start comms, open all channels -- start comms, open all channels
local superv_comms = supervisor.superv_comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN) local superv_comms = supervisor.comms(config.NUM_REACTORS, modem, config.SCADA_DEV_LISTEN, config.SCADA_SV_LISTEN)
-- base loop clock (6.67Hz, 3 ticks) -- base loop clock (6.67Hz, 3 ticks)
local MAIN_CLOCK = 0.15 local MAIN_CLOCK = 0.15
@ -61,9 +57,9 @@ while true do
-- we only care if this is our wireless modem -- we only care if this is our wireless modem
if device.dev == modem then if device.dev == modem then
println_ts("wireless modem disconnected!") println_ts("wireless modem disconnected!")
log._error("comms modem disconnected!") log.error("comms modem disconnected!")
else else
log._warning("non-comms modem disconnected") log.warning("non-comms modem disconnected")
end end
end end
elseif event == "peripheral" then elseif event == "peripheral" then
@ -76,9 +72,9 @@ while true do
superv_comms.reconnect_modem(modem) superv_comms.reconnect_modem(modem)
println_ts("wireless modem reconnected.") println_ts("wireless modem reconnected.")
log._info("comms modem reconnected.") log.info("comms modem reconnected.")
else else
log._info("wired modem reconnected.") log.info("wired modem reconnected.")
end end
end end
elseif event == "timer" and param1 == loop_clock then elseif event == "timer" and param1 == loop_clock then
@ -103,12 +99,12 @@ while true do
-- check for termination request -- check for termination request
if event == "terminate" or ppm.should_terminate() then if event == "terminate" or ppm.should_terminate() then
println_ts("closing sessions...") println_ts("closing sessions...")
log._info("terminate requested, closing sessions...") log.info("terminate requested, closing sessions...")
svsessions.close_all() svsessions.close_all()
log._info("sessions closed") log.info("sessions closed")
break break
end end
end end
println_ts("exited") println_ts("exited")
log._info("exited") log.info("exited")

View File

@ -1,7 +1,10 @@
-- #REQUIRES comms.lua local comms = require("scada-common.comms")
-- #REQUIRES mqueue.lua local log = require("scada-common.log")
-- #REQUIRES util.lua local util = require("scada-common.util")
-- #REQUIRES svsessions.lua
local svsessions = require("session.svsessions")
local supervisor = {}
local PROTOCOLS = comms.PROTOCOLS local PROTOCOLS = comms.PROTOCOLS
local RPLC_TYPES = comms.RPLC_TYPES local RPLC_TYPES = comms.RPLC_TYPES
@ -17,7 +20,7 @@ local print_ts = util.print_ts
local println_ts = util.println_ts local println_ts = util.println_ts
-- supervisory controller communications -- supervisory controller communications
function superv_comms(num_reactors, modem, dev_listen, coord_listen) supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen)
local self = { local self = {
ln_seq_num = 0, ln_seq_num = 0,
num_reactors = num_reactors, num_reactors = num_reactors,
@ -101,7 +104,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen)
pkt = coord_pkt.get() pkt = coord_pkt.get()
end end
else else
log._debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true)
end end
end end
@ -126,7 +129,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen)
if session then if session then
if packet.type == RPLC_TYPES.LINK_REQ then if packet.type == RPLC_TYPES.LINK_REQ then
-- new device on this port? that's a collision -- new device on this port? that's a collision
log._debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") log.debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision")
_send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) _send_plc_linking(r_port, { RPLC_LINKING.COLLISION })
else else
-- pass the packet onto the session handler -- pass the packet onto the session handler
@ -140,20 +143,20 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen)
local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1]) local plc_id = svsessions.establish_plc_session(l_port, r_port, packet.data[1])
if plc_id == false then if plc_id == false then
-- reactor already has a PLC assigned -- reactor already has a PLC assigned
log._debug("PLC_LNK: assignment collision with reactor " .. packet.data[1]) log.debug("PLC_LNK: assignment collision with reactor " .. packet.data[1])
_send_plc_linking(r_port, { RPLC_LINKING.COLLISION }) _send_plc_linking(r_port, { RPLC_LINKING.COLLISION })
else else
-- got an ID; assigned to a reactor successfully -- got an ID; assigned to a reactor successfully
println("connected to reactor " .. packet.data[1] .. " PLC (port " .. r_port .. ")") println("connected to reactor " .. packet.data[1] .. " PLC (port " .. r_port .. ")")
log._debug("PLC_LNK: allowed for device at " .. r_port) log.debug("PLC_LNK: allowed for device at " .. r_port)
_send_plc_linking(r_port, { RPLC_LINKING.ALLOW }) _send_plc_linking(r_port, { RPLC_LINKING.ALLOW })
end end
else else
log._debug("PLC_LNK: new linking packet length mismatch") log.debug("PLC_LNK: new linking packet length mismatch")
end end
else else
-- force a re-link -- force a re-link
log._debug("PLC_LNK: no session but not a link, force relink") log.debug("PLC_LNK: no session but not a link, force relink")
_send_plc_linking(r_port, { RPLC_LINKING.DENY }) _send_plc_linking(r_port, { RPLC_LINKING.DENY })
end end
end end
@ -164,7 +167,7 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen)
session.in_queue.push_packet(packet) session.in_queue.push_packet(packet)
end end
else else
log._debug("illegal packet type " .. protocol .. " on device listening channel") log.debug("illegal packet type " .. protocol .. " on device listening channel")
end end
-- coordinator listening channel -- coordinator listening channel
elseif l_port == self.coord_listen then elseif l_port == self.coord_listen then
@ -173,10 +176,10 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen)
elseif protocol == PROTOCOLS.COORD_DATA then elseif protocol == PROTOCOLS.COORD_DATA then
-- coordinator packet -- coordinator packet
else else
log._debug("illegal packet type " .. protocol .. " on coordinator listening channel") log.debug("illegal packet type " .. protocol .. " on coordinator listening channel")
end end
else else
log._error("received packet on unused channel " .. l_port, true) log.error("received packet on unused channel " .. l_port, true)
end end
end end
end end
@ -187,3 +190,5 @@ function superv_comms(num_reactors, modem, dev_listen, coord_listen)
handle_packet = handle_packet handle_packet = handle_packet
} }
end end
return supervisor