mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#33 lua module/require architecture changeover
This commit is contained in:
parent
7bcb260712
commit
b575899d46
@ -1,4 +1,4 @@
|
|||||||
-- #REQUIRES comms.lua
|
local comms = require("scada-common.comms")
|
||||||
|
|
||||||
-- coordinator communications
|
-- coordinator communications
|
||||||
function coord_comms()
|
function coord_comms()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
31
rtu/rtu.lua
31
rtu/rtu.lua
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
local coordinator = {}
|
||||||
|
|
||||||
|
return coordinator
|
@ -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
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
local rtu = {}
|
||||||
|
|
||||||
|
return rtu
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user