Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a7cb9eaa8 | ||
|
|
3139dc2176 | ||
|
|
25fc0050c3 | ||
|
|
390cf98b0a | ||
|
|
7ddd6f32c5 | ||
|
|
18a488f1b9 | ||
|
|
4c7ad0c539 | ||
|
|
a083f8983b |
@ -1,57 +0,0 @@
|
|||||||
# Contribution Guide
|
|
||||||
|
|
||||||
>[!NOTE]
|
|
||||||
Until the system is out of beta, contributions will be limited as I wrap up the specific release feature set.
|
|
||||||
|
|
||||||
This project is highly complex for a ComputerCraft Lua application. Contributions need to follow style guides and meet the code quality I've kept this project up to for years. Contributions must be tested appropriately with test results included.
|
|
||||||
|
|
||||||
I have extensively tested software components for stability required for safety, with tiers of software robustness.
|
|
||||||
1. **Critical: High-Impact** -
|
|
||||||
The Reactor-PLC is "uncrashable" and must remain so. I've extensively reviewed every line and behavior, so any code contributions must be at this high standard. Simple is stable, so the less code the better. Always check for parameter validity and extensively test any changes to critical thread functions.
|
|
||||||
2. **Important: Moderate-Impact** -
|
|
||||||
The Supervisor and RTU Gateway should rarely, if ever, crash. Certain places may not be held to as strict of a level as above, but should be written understanding all the possible inputs to and impacts of a section of code.
|
|
||||||
3. **Useful: Low-Impact** -
|
|
||||||
The Coordinator and Pocket are nice UI apps, and things can break. There's a lot of data going to and from them, so checking every single incoming value would have negative performance impacts and increase program size. If they break, the user can restart them. Don't introduce careless bugs, but making assumptions about the integrity of incoming data is acceptable.
|
|
||||||
|
|
||||||
## Valuable Contributions
|
|
||||||
|
|
||||||
Pull requests should not consist of purely whitespace changes, comment changes, or other trivial changes. They should target specific features, bug fixes, or functional improvements. I reserve the right to decline PRs that don't follow this in good faith.
|
|
||||||
|
|
||||||
## Project Management Guidelines
|
|
||||||
|
|
||||||
Any contributions should be linked to an open GitHub issue. These are used to track progress, discuss changes, etc. Surprise changes to this project might conflict with existing plans, so I prefer we coordinate changes ahead of time.
|
|
||||||
|
|
||||||
## Software Guidelines
|
|
||||||
|
|
||||||
These guidelines are subject to change. The general rule is make the code look like the rest of the code around it and elsewhere in the project.
|
|
||||||
|
|
||||||
### Style Guide
|
|
||||||
|
|
||||||
PRs will only be accepted if they match the style of this project and pass manual and automated code analysis. Listing out the whole style guide would take a while, so as stated above, please review code adjacent to your modifications.
|
|
||||||
|
|
||||||
1. **No Block Comments.**
|
|
||||||
These interfere with the minification used for the bundled installation files due to the complexity of parsing Lua block comments. The minification code is meant to be simple to have 0 risk of breaking anything, so I'm staying far away from those.
|
|
||||||
2. **Comment Your Code.**
|
|
||||||
This includes type hints as used elsewhere throughout the project. Your comments should be associated with parts of code that are more complex or unclear, or otherwise to split up sections of tasks. You'll see `--#region` used in various places.
|
|
||||||
- Type hints are intended to be utilized by the `sumneko.lua` vscode extension. You should use this while developing, as it provides extremely valuable functionality.
|
|
||||||
3. **Whitespace Usage.**
|
|
||||||
Whitespace should be used to separate function parameters and operators. The one exception is the unique styling of graphics elements, which you should compare against if modifying them.
|
|
||||||
- 4 spaces are used for all indentation.
|
|
||||||
- Try to align assignment operator lines as is done elsewhere (adding space before `=`).
|
|
||||||
- Use empty new lines to separate steps or distinct groups of operations.
|
|
||||||
- Generally add new lines for each step in loops and for statements. For some single-line ones, they may be compressed into a single line. This saves on space utilization, especially on deeply indented lines.
|
|
||||||
4. **Variables and Classes.**
|
|
||||||
- Variables, functions, and class-like tables follow the snake_case convention.
|
|
||||||
- Graphics objects and configuration settings follow PascalCase.
|
|
||||||
- Constants follow all-caps SNAKE_CASE and local ones should be declared at the top of files after `require` statements and external ones (like `local ALARM = types.ALARM`).
|
|
||||||
5. **No `goto`.**
|
|
||||||
These are generally frowned upon due to reducing code readability.
|
|
||||||
6. **Multiple `return`s.**
|
|
||||||
These are allowed to minimize code size, but if it is simple to avoid multiple, do so.
|
|
||||||
7. **Classes and Objects.**
|
|
||||||
Review the existing code for examples on how objects are implemented in this project. They do not use Lua's `:` operator and `self` functionality. A manual object-like table definition is used. Some global single-instance classes don't use a `new()` function, such as the [PPM](https://github.com/MikaylaFischler/cc-mek-scada/blob/main/scada-common/ppm.lua). Multi-instance ones do, such as the Supervisor's [unit](https://github.com/MikaylaFischler/cc-mek-scada/blob/main/supervisor/unit.lua) class.
|
|
||||||
|
|
||||||
### No AI
|
|
||||||
|
|
||||||
Your code should follow the style guide, be succinct, make sense, and you should be able to explain what it does. Random changes done in multiple places will be deemed suspicious along with poor comments or nonsensical code.
|
|
||||||
Use your contributions as programming practice or to hone your skills; don't automate away thinking.
|
|
||||||
@ -45,7 +45,6 @@ v10.1+ is required due to the complete support of CC:Tweaked added in Mekanism v
|
|||||||
You can install this on a ComputerCraft computer using either:
|
You can install this on a ComputerCraft computer using either:
|
||||||
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
||||||
* `pastebin get sqUN6VUb ccmsi.lua`
|
* `pastebin get sqUN6VUb ccmsi.lua`
|
||||||
* Off-line (when HTTP is disabled) installation via [release bundles](https://github.com/MikaylaFischler/cc-mek-scada/wiki/Alternative-Installation-Strategies#release-bundles)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ local CCMSI_VERSION = "v1.21"
|
|||||||
|
|
||||||
local install_dir = "/.install-cache"
|
local install_dir = "/.install-cache"
|
||||||
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
||||||
local repo_path = "http://git.befatorinc.de/TheHomecraft/cc-mek-scada/raw/"
|
local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/"
|
||||||
|
|
||||||
---@diagnostic disable-next-line: undefined-global
|
---@diagnostic disable-next-line: undefined-global
|
||||||
local _is_pkt_env = pocket -- luacheck: ignore pocket
|
local _is_pkt_env = pocket -- luacheck: ignore pocket
|
||||||
|
|||||||
@ -53,7 +53,6 @@ function databus.tx_hw_status(plc_state)
|
|||||||
databus.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2)))
|
databus.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2)))
|
||||||
databus.ps.publish("has_modem", not plc_state.no_modem)
|
databus.ps.publish("has_modem", not plc_state.no_modem)
|
||||||
databus.ps.publish("degraded", plc_state.degraded)
|
databus.ps.publish("degraded", plc_state.degraded)
|
||||||
databus.ps.publish("init_ok", plc_state.init_ok)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- transmit thread (routine) statuses
|
-- transmit thread (routine) statuses
|
||||||
|
|||||||
@ -51,11 +51,11 @@ local function init(panel)
|
|||||||
|
|
||||||
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
||||||
|
|
||||||
local init_ok = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
local degraded = LED{parent=system,label="STATUS",colors=cpair(colors.red,colors.green)}
|
||||||
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
|
||||||
system.line_break()
|
system.line_break()
|
||||||
|
|
||||||
init_ok.register(databus.ps, "init_ok", init_ok.update)
|
degraded.register(databus.ps, "degraded", degraded.update)
|
||||||
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
||||||
|
|
||||||
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
|
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
|
||||||
|
|||||||
@ -118,7 +118,7 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
reactor_enabled = false,
|
reactor_enabled = false,
|
||||||
enabled_at = 0,
|
enabled_at = 0,
|
||||||
emer_cool_active = nil, ---@type boolean
|
emer_cool_active = nil, ---@type boolean
|
||||||
formed = is_formed,
|
formed = is_formed, ---@type boolean|nil
|
||||||
force_disabled = false,
|
force_disabled = false,
|
||||||
tripped = false,
|
tripped = false,
|
||||||
trip_cause = "ok" ---@type rps_trip_cause
|
trip_cause = "ok" ---@type rps_trip_cause
|
||||||
@ -364,14 +364,16 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
return public.activate()
|
return public.activate()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check all safety conditions
|
-- check all safety conditions if we have a formed reactor, otherwise handle a subset of conditions
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@param has_reactor boolean if the PLC state indicates we have a reactor
|
||||||
---@return boolean tripped, rps_trip_cause trip_status, boolean first_trip
|
---@return boolean tripped, rps_trip_cause trip_status, boolean first_trip
|
||||||
function public.check()
|
function public.check(has_reactor)
|
||||||
local status = RPS_TRIP_CAUSE.OK
|
local status = RPS_TRIP_CAUSE.OK
|
||||||
local was_tripped = self.tripped
|
local was_tripped = self.tripped
|
||||||
local first_trip = false
|
local first_trip = false
|
||||||
|
|
||||||
|
if has_reactor then
|
||||||
if self.formed then
|
if self.formed then
|
||||||
-- update state
|
-- update state
|
||||||
parallel.waitForAll(
|
parallel.waitForAll(
|
||||||
@ -388,6 +390,10 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
-- check to see if its now formed
|
-- check to see if its now formed
|
||||||
_is_formed()
|
_is_formed()
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
self.formed = nil
|
||||||
|
self.state[CHK.SYS_FAIL] = true
|
||||||
|
end
|
||||||
|
|
||||||
-- check system states in order of severity
|
-- check system states in order of severity
|
||||||
if self.tripped then
|
if self.tripped then
|
||||||
@ -474,6 +480,7 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_active() return self.reactor_enabled end
|
function public.is_active() return self.reactor_enabled end
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
---@return boolean|nil formed true if formed, false if not, nil if unknown
|
||||||
function public.is_formed() return self.formed end
|
function public.is_formed() return self.formed end
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_force_disabled() return self.force_disabled end
|
function public.is_force_disabled() return self.force_disabled end
|
||||||
@ -495,14 +502,14 @@ function plc.rps_init(reactor, is_formed)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- partial RPS reset that only clears fault and sys_fail
|
-- partial RPS reset that only clears fault and sys_fail
|
||||||
function public.reset_formed()
|
function public.reset_reattach()
|
||||||
self.tripped = false
|
self.tripped = false
|
||||||
self.trip_cause = RPS_TRIP_CAUSE.OK
|
self.trip_cause = RPS_TRIP_CAUSE.OK
|
||||||
|
|
||||||
self.state[CHK.FAULT] = false
|
self.state[CHK.FAULT] = false
|
||||||
self.state[CHK.SYS_FAIL] = false
|
self.state[CHK.SYS_FAIL] = false
|
||||||
|
|
||||||
log.info("RPS: partial reset on formed")
|
log.info("RPS: partial reset on connected or formed")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- reset the automatic and timeout trip flags, then clear trip if that was the trip cause
|
-- reset the automatic and timeout trip flags, then clear trip if that was the trip cause
|
||||||
@ -584,11 +591,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
-- dynamic reactor status information, excluding heating rate
|
-- dynamic reactor status information, excluding heating rate
|
||||||
---@return table data_table, boolean faulted
|
---@return table data_table, boolean faulted
|
||||||
local function _get_reactor_status()
|
local function _get_reactor_status()
|
||||||
local fuel = nil
|
local fuel, waste, coolant, hcoolant = nil, nil, nil, nil
|
||||||
local waste = nil
|
|
||||||
local coolant = nil
|
|
||||||
local hcoolant = nil
|
|
||||||
|
|
||||||
local data_table = {}
|
local data_table = {}
|
||||||
|
|
||||||
reactor.__p_disable_afc()
|
reactor.__p_disable_afc()
|
||||||
@ -707,6 +710,112 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
reactor.__p_enable_afc()
|
reactor.__p_enable_afc()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- handle a burn rate command
|
||||||
|
---@param packet rplc_frame
|
||||||
|
---@param setpoints plc_setpoints
|
||||||
|
--- EVENT_CONSUMER: this function consumes events
|
||||||
|
local function _handle_burn_rate(packet, setpoints)
|
||||||
|
if (packet.length == 2) and (type(packet.data[1]) == "number") then
|
||||||
|
local success = false
|
||||||
|
local burn_rate = math.floor(packet.data[1] * 10) / 10
|
||||||
|
local ramp = packet.data[2]
|
||||||
|
|
||||||
|
-- if no known max burn rate, check again
|
||||||
|
if self.max_burn_rate == nil then
|
||||||
|
self.max_burn_rate = reactor.getMaxBurnRate()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if we know our max burn rate, update current burn rate setpoint if in range
|
||||||
|
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
||||||
|
if burn_rate > 0 and burn_rate <= self.max_burn_rate then
|
||||||
|
if ramp then
|
||||||
|
setpoints.burn_rate_en = true
|
||||||
|
setpoints.burn_rate = burn_rate
|
||||||
|
success = true
|
||||||
|
else
|
||||||
|
reactor.setBurnRate(burn_rate)
|
||||||
|
success = reactor.__p_is_ok()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_ack(packet.type, success)
|
||||||
|
else
|
||||||
|
log.debug("RPLC set burn rate packet length mismatch or non-numeric burn rate")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle an auto burn rate command
|
||||||
|
---@param packet rplc_frame
|
||||||
|
---@param setpoints plc_setpoints
|
||||||
|
--- EVENT_CONSUMER: this function consumes events
|
||||||
|
local function _handle_auto_burn_rate(packet, setpoints)
|
||||||
|
if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then
|
||||||
|
local ack = AUTO_ACK.FAIL
|
||||||
|
local burn_rate = math.floor(packet.data[1] * 100) / 100
|
||||||
|
local ramp = packet.data[2]
|
||||||
|
self.auto_ack_token = packet.data[3]
|
||||||
|
|
||||||
|
-- if no known max burn rate, check again
|
||||||
|
if self.max_burn_rate == nil then
|
||||||
|
self.max_burn_rate = reactor.getMaxBurnRate()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if we know our max burn rate, update current burn rate setpoint if in range
|
||||||
|
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
||||||
|
if burn_rate < 0.01 then
|
||||||
|
if rps.is_active() then
|
||||||
|
-- auto scram to disable
|
||||||
|
log.debug("AUTO: stopping the reactor to meet 0.0 burn rate")
|
||||||
|
if rps.scram() then
|
||||||
|
ack = AUTO_ACK.ZERO_DIS_OK
|
||||||
|
else
|
||||||
|
log.warning("AUTO: automatic reactor stop failed")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ack = AUTO_ACK.ZERO_DIS_OK
|
||||||
|
end
|
||||||
|
elseif burn_rate <= self.max_burn_rate then
|
||||||
|
if not rps.is_active() then
|
||||||
|
-- activate the reactor
|
||||||
|
log.debug("AUTO: activating the reactor")
|
||||||
|
|
||||||
|
reactor.setBurnRate(0.01)
|
||||||
|
if reactor.__p_is_faulted() then
|
||||||
|
log.warning("AUTO: failed to reset burn rate for auto activation")
|
||||||
|
else
|
||||||
|
if not rps.auto_activate() then
|
||||||
|
log.warning("AUTO: automatic reactor activation failed")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if active, set/ramp burn rate
|
||||||
|
if rps.is_active() then
|
||||||
|
if ramp then
|
||||||
|
log.debug(util.c("AUTO: setting burn rate ramp to ", burn_rate))
|
||||||
|
setpoints.burn_rate_en = true
|
||||||
|
setpoints.burn_rate = burn_rate
|
||||||
|
ack = AUTO_ACK.RAMP_SET_OK
|
||||||
|
else
|
||||||
|
log.debug(util.c("AUTO: setting burn rate directly to ", burn_rate))
|
||||||
|
reactor.setBurnRate(burn_rate)
|
||||||
|
ack = util.trinary(reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug(util.c(burn_rate, " rate outside of 0 < x <= ", self.max_burn_rate))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_ack(packet.type, ack)
|
||||||
|
else
|
||||||
|
log.debug("RPLC set automatic burn rate packet length mismatch or non-numeric burn rate")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- PUBLIC FUNCTIONS --
|
-- PUBLIC FUNCTIONS --
|
||||||
|
|
||||||
---@class plc_comms
|
---@class plc_comms
|
||||||
@ -803,15 +912,11 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
-- get as RPLC packet
|
-- get as RPLC packet
|
||||||
if s_pkt.protocol() == PROTOCOL.RPLC then
|
if s_pkt.protocol() == PROTOCOL.RPLC then
|
||||||
local rplc_pkt = comms.rplc_packet()
|
local rplc_pkt = comms.rplc_packet()
|
||||||
if rplc_pkt.decode(s_pkt) then
|
if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end
|
||||||
pkt = rplc_pkt.get()
|
|
||||||
end
|
|
||||||
-- get as SCADA management packet
|
-- get as SCADA management packet
|
||||||
elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||||
local mgmt_pkt = comms.mgmt_packet()
|
local mgmt_pkt = comms.mgmt_packet()
|
||||||
if mgmt_pkt.decode(s_pkt) then
|
if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() end
|
||||||
pkt = mgmt_pkt.get()
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("unsupported packet type " .. s_pkt.protocol(), true)
|
log.debug("unsupported packet type " .. s_pkt.protocol(), true)
|
||||||
end
|
end
|
||||||
@ -823,16 +928,13 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
-- handle RPLC and MGMT packets
|
-- handle RPLC and MGMT packets
|
||||||
---@param packet rplc_frame|mgmt_frame packet frame
|
---@param packet rplc_frame|mgmt_frame packet frame
|
||||||
---@param plc_state plc_state PLC state
|
---@param plc_state plc_state PLC state
|
||||||
---@param setpoints setpoints setpoint control table
|
---@param setpoints plc_setpoints setpoint control table
|
||||||
function public.handle_packet(packet, plc_state, setpoints)
|
---@param println_ts function console print, when UI isn't running
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
function public.handle_packet(packet, plc_state, setpoints, println_ts)
|
||||||
local function println_ts(message) if not plc_state.fp_ok then util.println_ts(message) end end
|
|
||||||
|
|
||||||
local protocol = packet.scada_frame.protocol()
|
local protocol = packet.scada_frame.protocol()
|
||||||
local l_chan = packet.scada_frame.local_channel()
|
local l_chan = packet.scada_frame.local_channel()
|
||||||
local src_addr = packet.scada_frame.src_addr()
|
local src_addr = packet.scada_frame.src_addr()
|
||||||
|
|
||||||
-- handle packets now that we have prints setup
|
|
||||||
if l_chan == config.PLC_Channel then
|
if l_chan == config.PLC_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
@ -867,36 +969,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
log.debug("sent out structure again, did supervisor miss it?")
|
log.debug("sent out structure again, did supervisor miss it?")
|
||||||
elseif packet.type == RPLC_TYPE.MEK_BURN_RATE then
|
elseif packet.type == RPLC_TYPE.MEK_BURN_RATE then
|
||||||
-- set the burn rate
|
-- set the burn rate
|
||||||
if (packet.length == 2) and (type(packet.data[1]) == "number") then
|
_handle_burn_rate(packet, setpoints)
|
||||||
local success = false
|
|
||||||
local burn_rate = math.floor(packet.data[1] * 10) / 10
|
|
||||||
local ramp = packet.data[2]
|
|
||||||
|
|
||||||
-- if no known max burn rate, check again
|
|
||||||
if self.max_burn_rate == nil then
|
|
||||||
self.max_burn_rate = reactor.getMaxBurnRate()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if we know our max burn rate, update current burn rate setpoint if in range
|
|
||||||
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
|
||||||
if burn_rate > 0 and burn_rate <= self.max_burn_rate then
|
|
||||||
if ramp then
|
|
||||||
setpoints.burn_rate_en = true
|
|
||||||
setpoints.burn_rate = burn_rate
|
|
||||||
success = true
|
|
||||||
else
|
|
||||||
reactor.setBurnRate(burn_rate)
|
|
||||||
success = reactor.__p_is_ok()
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_ack(packet.type, success)
|
|
||||||
else
|
|
||||||
log.debug("RPLC set burn rate packet length mismatch or non-numeric burn rate")
|
|
||||||
end
|
|
||||||
elseif packet.type == RPLC_TYPE.RPS_ENABLE then
|
elseif packet.type == RPLC_TYPE.RPS_ENABLE then
|
||||||
-- enable the reactor
|
-- enable the reactor
|
||||||
self.scrammed = false
|
self.scrammed = false
|
||||||
@ -925,68 +998,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
|
|||||||
_send_ack(packet.type, true)
|
_send_ack(packet.type, true)
|
||||||
elseif packet.type == RPLC_TYPE.AUTO_BURN_RATE then
|
elseif packet.type == RPLC_TYPE.AUTO_BURN_RATE then
|
||||||
-- automatic control requested a new burn rate
|
-- automatic control requested a new burn rate
|
||||||
if (packet.length == 3) and (type(packet.data[1]) == "number") and (type(packet.data[3]) == "number") then
|
_handle_auto_burn_rate(packet, setpoints)
|
||||||
local ack = AUTO_ACK.FAIL
|
|
||||||
local burn_rate = math.floor(packet.data[1] * 100) / 100
|
|
||||||
local ramp = packet.data[2]
|
|
||||||
self.auto_ack_token = packet.data[3]
|
|
||||||
|
|
||||||
-- if no known max burn rate, check again
|
|
||||||
if self.max_burn_rate == nil then
|
|
||||||
self.max_burn_rate = reactor.getMaxBurnRate()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if we know our max burn rate, update current burn rate setpoint if in range
|
|
||||||
if self.max_burn_rate ~= ppm.ACCESS_FAULT then
|
|
||||||
if burn_rate < 0.01 then
|
|
||||||
if rps.is_active() then
|
|
||||||
-- auto scram to disable
|
|
||||||
log.debug("AUTO: stopping the reactor to meet 0.0 burn rate")
|
|
||||||
if rps.scram() then
|
|
||||||
ack = AUTO_ACK.ZERO_DIS_OK
|
|
||||||
else
|
|
||||||
log.warning("AUTO: automatic reactor stop failed")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ack = AUTO_ACK.ZERO_DIS_OK
|
|
||||||
end
|
|
||||||
elseif burn_rate <= self.max_burn_rate then
|
|
||||||
if not rps.is_active() then
|
|
||||||
-- activate the reactor
|
|
||||||
log.debug("AUTO: activating the reactor")
|
|
||||||
|
|
||||||
reactor.setBurnRate(0.01)
|
|
||||||
if reactor.__p_is_faulted() then
|
|
||||||
log.warning("AUTO: failed to reset burn rate for auto activation")
|
|
||||||
else
|
|
||||||
if not rps.auto_activate() then
|
|
||||||
log.warning("AUTO: automatic reactor activation failed")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if active, set/ramp burn rate
|
|
||||||
if rps.is_active() then
|
|
||||||
if ramp then
|
|
||||||
log.debug(util.c("AUTO: setting burn rate ramp to ", burn_rate))
|
|
||||||
setpoints.burn_rate_en = true
|
|
||||||
setpoints.burn_rate = burn_rate
|
|
||||||
ack = AUTO_ACK.RAMP_SET_OK
|
|
||||||
else
|
|
||||||
log.debug(util.c("AUTO: setting burn rate directly to ", burn_rate))
|
|
||||||
reactor.setBurnRate(burn_rate)
|
|
||||||
ack = util.trinary(reactor.__p_is_faulted(), AUTO_ACK.FAIL, AUTO_ACK.DIRECT_SET_OK)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.debug(util.c(burn_rate, " rate outside of 0 < x <= ", self.max_burn_rate))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
_send_ack(packet.type, ack)
|
|
||||||
else
|
|
||||||
log.debug("RPLC set automatic burn rate packet length mismatch or non-numeric burn rate")
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("received unknown RPLC packet type " .. packet.type)
|
log.debug("received unknown RPLC packet type " .. packet.type)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
|||||||
local renderer = require("reactor-plc.renderer")
|
local renderer = require("reactor-plc.renderer")
|
||||||
local threads = require("reactor-plc.threads")
|
local threads = require("reactor-plc.threads")
|
||||||
|
|
||||||
local R_PLC_VERSION = "v1.8.22"
|
local R_PLC_VERSION = "v1.9.0"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -87,7 +87,6 @@ local function main()
|
|||||||
-- PLC system state flags
|
-- PLC system state flags
|
||||||
---@class plc_state
|
---@class plc_state
|
||||||
plc_state = {
|
plc_state = {
|
||||||
init_ok = true,
|
|
||||||
fp_ok = false,
|
fp_ok = false,
|
||||||
shutdown = false,
|
shutdown = false,
|
||||||
degraded = true,
|
degraded = true,
|
||||||
@ -97,19 +96,22 @@ local function main()
|
|||||||
},
|
},
|
||||||
|
|
||||||
-- control setpoints
|
-- control setpoints
|
||||||
---@class setpoints
|
---@class plc_setpoints
|
||||||
setpoints = {
|
setpoints = {
|
||||||
burn_rate_en = false,
|
burn_rate_en = false,
|
||||||
burn_rate = 0.0
|
burn_rate = 0.0
|
||||||
},
|
},
|
||||||
|
|
||||||
-- core PLC devices
|
-- core PLC devices
|
||||||
|
---@class plc_dev
|
||||||
plc_dev = {
|
plc_dev = {
|
||||||
reactor = ppm.get_fission_reactor(),
|
---@diagnostic disable-next-line: assign-type-mismatch
|
||||||
|
reactor = ppm.get_fission_reactor(), ---@type table
|
||||||
modem = ppm.get_wireless_modem()
|
modem = ppm.get_wireless_modem()
|
||||||
},
|
},
|
||||||
|
|
||||||
-- system objects
|
-- system objects
|
||||||
|
---@class plc_sys
|
||||||
plc_sys = {
|
plc_sys = {
|
||||||
rps = nil, ---@type rps
|
rps = nil, ---@type rps
|
||||||
nic = nil, ---@type nic
|
nic = nil, ---@type nic
|
||||||
@ -136,14 +138,20 @@ local function main()
|
|||||||
|
|
||||||
-- we need a reactor, can at least do some things even if it isn't formed though
|
-- we need a reactor, can at least do some things even if it isn't formed though
|
||||||
if plc_state.no_reactor then
|
if plc_state.no_reactor then
|
||||||
println("init> fission reactor not found")
|
println("startup> fission reactor not found")
|
||||||
log.warning("init> no reactor on startup")
|
log.warning("startup> no reactor on startup")
|
||||||
|
|
||||||
plc_state.init_ok = false
|
|
||||||
plc_state.degraded = true
|
plc_state.degraded = true
|
||||||
|
plc_state.reactor_formed = false
|
||||||
|
|
||||||
|
-- mount a virtual peripheral to init the RPS with
|
||||||
|
local _, dev = ppm.mount_virtual()
|
||||||
|
smem_dev.reactor = dev
|
||||||
|
|
||||||
|
log.info("startup> mounted virtual device as reactor")
|
||||||
elseif not smem_dev.reactor.isFormed() then
|
elseif not smem_dev.reactor.isFormed() then
|
||||||
println("init> fission reactor is not formed")
|
println("startup> fission reactor is not formed")
|
||||||
log.warning("init> reactor logic adapter present, but reactor is not formed")
|
log.warning("startup> reactor logic adapter present, but reactor is not formed")
|
||||||
|
|
||||||
plc_state.degraded = true
|
plc_state.degraded = true
|
||||||
plc_state.reactor_formed = false
|
plc_state.reactor_formed = false
|
||||||
@ -151,89 +159,74 @@ local function main()
|
|||||||
|
|
||||||
-- modem is required if networked
|
-- modem is required if networked
|
||||||
if __shared_memory.networked and plc_state.no_modem then
|
if __shared_memory.networked and plc_state.no_modem then
|
||||||
println("init> wireless modem not found")
|
println("startup> wireless modem not found")
|
||||||
log.warning("init> no wireless modem on startup")
|
log.warning("startup> no wireless modem on startup")
|
||||||
|
|
||||||
-- scram reactor if present and enabled
|
-- scram reactor if present and enabled
|
||||||
if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||||
smem_dev.reactor.scram()
|
smem_dev.reactor.scram()
|
||||||
end
|
end
|
||||||
|
|
||||||
plc_state.init_ok = false
|
|
||||||
plc_state.degraded = true
|
plc_state.degraded = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
|
||||||
local function _println_no_fp(message) if not plc_state.fp_ok then println(message) end end
|
|
||||||
|
|
||||||
-- PLC init<br>
|
|
||||||
--- EVENT_CONSUMER: this function consumes events
|
|
||||||
local function init()
|
|
||||||
-- scram on boot if networked, otherwise leave the reactor be
|
-- scram on boot if networked, otherwise leave the reactor be
|
||||||
if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
|
||||||
|
log.debug("startup> power-on SCRAM")
|
||||||
smem_dev.reactor.scram()
|
smem_dev.reactor.scram()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- setup front panel
|
-- setup front panel
|
||||||
if not renderer.ui_ready() then
|
|
||||||
local message
|
local message
|
||||||
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
|
||||||
|
|
||||||
-- ...or not
|
-- ...or not
|
||||||
if not plc_state.fp_ok then
|
if not plc_state.fp_ok then
|
||||||
println_ts(util.c("UI error: ", message))
|
println_ts(util.c("UI error: ", message))
|
||||||
println("init> running without front panel")
|
println("startup> running without front panel")
|
||||||
log.error(util.c("front panel GUI render failed with error ", message))
|
log.error(util.c("front panel GUI render failed with error ", message))
|
||||||
log.info("init> running in headless mode without front panel")
|
log.info("startup> running in headless mode without front panel")
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if plc_state.init_ok then
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
|
local function _println_no_fp(msg) if not plc_state.fp_ok then println(msg) end end
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
-- initialize PLC
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
-- init reactor protection system
|
-- init reactor protection system
|
||||||
smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed)
|
smem_sys.rps = plc.rps_init(smem_dev.reactor, util.trinary(plc_state.no_reactor, nil, plc_state.reactor_formed))
|
||||||
log.debug("init> rps init")
|
log.debug("startup> rps init")
|
||||||
|
|
||||||
|
-- notify user of emergency coolant configuration status
|
||||||
|
if config.EmerCoolEnable then
|
||||||
|
_println_no_fp("startup> emergency coolant control ready")
|
||||||
|
log.info("startup> emergency coolant control available")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- conditionally init comms
|
||||||
if __shared_memory.networked then
|
if __shared_memory.networked then
|
||||||
-- comms watchdog
|
-- comms watchdog
|
||||||
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||||
log.debug("init> conn watchdog started")
|
log.debug("startup> conn watchdog started")
|
||||||
|
|
||||||
-- create network interface then setup comms
|
-- create network interface then setup comms
|
||||||
smem_sys.nic = network.nic(smem_dev.modem)
|
smem_sys.nic = network.nic(smem_dev.modem)
|
||||||
smem_sys.plc_comms = plc.comms(R_PLC_VERSION, smem_sys.nic, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
|
smem_sys.plc_comms = plc.comms(R_PLC_VERSION, smem_sys.nic, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
|
||||||
log.debug("init> comms init")
|
log.debug("startup> comms init")
|
||||||
else
|
else
|
||||||
_println_no_fp("init> starting in offline mode")
|
_println_no_fp("startup> starting in non-networked mode")
|
||||||
log.info("init> running without networking")
|
log.info("startup> starting without networking")
|
||||||
end
|
|
||||||
|
|
||||||
-- notify user of emergency coolant configuration status
|
|
||||||
if config.EmerCoolEnable then
|
|
||||||
println("init> emergency coolant control ready")
|
|
||||||
log.info("init> running with emergency coolant control available")
|
|
||||||
end
|
|
||||||
|
|
||||||
util.push_event("clock_start")
|
|
||||||
|
|
||||||
_println_no_fp("init> completed")
|
|
||||||
log.info("init> startup completed")
|
|
||||||
else
|
|
||||||
_println_no_fp("init> system in degraded state, awaiting devices...")
|
|
||||||
log.warning("init> started in a degraded state, awaiting peripheral connections...")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
databus.tx_hw_status(plc_state)
|
databus.tx_hw_status(plc_state)
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------
|
_println_no_fp("startup> completed")
|
||||||
-- start system
|
log.info("startup> completed")
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
-- initialize PLC
|
|
||||||
init()
|
|
||||||
|
|
||||||
-- init threads
|
-- init threads
|
||||||
local main_thread = threads.thread__main(__shared_memory, init)
|
local main_thread = threads.thread__main(__shared_memory)
|
||||||
local rps_thread = threads.thread__rps(__shared_memory)
|
local rps_thread = threads.thread__rps(__shared_memory)
|
||||||
|
|
||||||
if __shared_memory.networked then
|
if __shared_memory.networked then
|
||||||
@ -247,14 +240,12 @@ local function main()
|
|||||||
-- run threads
|
-- run threads
|
||||||
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec, comms_thread_tx.p_exec, comms_thread_rx.p_exec, sp_ctrl_thread.p_exec)
|
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec, comms_thread_tx.p_exec, comms_thread_rx.p_exec, sp_ctrl_thread.p_exec)
|
||||||
|
|
||||||
if plc_state.init_ok then
|
|
||||||
-- send status one last time after RPS shutdown
|
-- send status one last time after RPS shutdown
|
||||||
smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||||
smem_sys.plc_comms.send_rps_status()
|
smem_sys.plc_comms.send_rps_status()
|
||||||
|
|
||||||
-- close connection
|
-- close connection
|
||||||
smem_sys.plc_comms.close()
|
smem_sys.plc_comms.close()
|
||||||
end
|
|
||||||
else
|
else
|
||||||
-- run threads, excluding comms
|
-- run threads, excluding comms
|
||||||
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec)
|
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec)
|
||||||
|
|||||||
@ -31,8 +31,7 @@ local MQ__COMM_CMD = {
|
|||||||
-- main thread
|
-- main thread
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param smem plc_shared_memory
|
---@param smem plc_shared_memory
|
||||||
---@param init function
|
function threads.thread__main(smem)
|
||||||
function threads.thread__main(smem, init)
|
|
||||||
-- print a log message to the terminal as long as the UI isn't running
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end
|
local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ function threads.thread__main(smem, init)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("main", true)
|
databus.tx_rt_status("main", true)
|
||||||
log.debug("main thread init, clock inactive")
|
log.debug("OS: main thread start")
|
||||||
|
|
||||||
-- 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 8 loop ticks)
|
-- send link requests at 0.5Hz (every 40 server ticks) (every 8 loop ticks)
|
||||||
@ -55,6 +54,9 @@ function threads.thread__main(smem, init)
|
|||||||
local plc_state = smem.plc_state
|
local plc_state = smem.plc_state
|
||||||
local plc_dev = smem.plc_dev
|
local plc_dev = smem.plc_dev
|
||||||
|
|
||||||
|
-- start clock
|
||||||
|
loop_clock.start()
|
||||||
|
|
||||||
-- event loop
|
-- event loop
|
||||||
while true do
|
while true do
|
||||||
-- get plc_sys fields (may have been set late due to degraded boot)
|
-- get plc_sys fields (may have been set late due to degraded boot)
|
||||||
@ -67,7 +69,6 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
if event == "timer" and loop_clock.is_clock(param1) then
|
if event == "timer" and loop_clock.is_clock(param1) then
|
||||||
-- note: loop clock is only running if init_ok = true
|
|
||||||
-- blink heartbeat indicator
|
-- blink heartbeat indicator
|
||||||
databus.heartbeat()
|
databus.heartbeat()
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ function threads.thread__main(smem, init)
|
|||||||
-- reactor now formed
|
-- reactor now formed
|
||||||
plc_state.reactor_formed = true
|
plc_state.reactor_formed = true
|
||||||
|
|
||||||
println_ts("reactor is now formed.")
|
println_ts("reactor is now formed")
|
||||||
log.info("reactor is now formed")
|
log.info("reactor is now formed")
|
||||||
|
|
||||||
-- SCRAM newly formed reactor
|
-- SCRAM newly formed reactor
|
||||||
@ -106,10 +107,10 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- partial reset of RPS, specific to becoming formed
|
-- partial reset of RPS, specific to becoming formed
|
||||||
-- without this, auto control can't resume on chunk load
|
-- without this, auto control can't resume on chunk load
|
||||||
rps.reset_formed()
|
rps.reset_reattach()
|
||||||
elseif plc_state.reactor_formed and not rps.is_formed() then
|
elseif plc_state.reactor_formed and (rps.is_formed() == false) then
|
||||||
-- reactor no longer formed
|
-- reactor no longer formed
|
||||||
println_ts("reactor is no longer formed.")
|
println_ts("reactor is no longer formed")
|
||||||
log.info("reactor is no longer formed")
|
log.info("reactor is no longer formed")
|
||||||
|
|
||||||
plc_state.reactor_formed = false
|
plc_state.reactor_formed = false
|
||||||
@ -118,14 +119,14 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- update indicators
|
-- update indicators
|
||||||
databus.tx_hw_status(plc_state)
|
databus.tx_hw_status(plc_state)
|
||||||
elseif event == "modem_message" and networked and plc_state.init_ok and nic.is_connected() then
|
elseif event == "modem_message" and networked and nic.is_connected() then
|
||||||
-- got a packet
|
-- got a packet
|
||||||
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||||
if packet ~= nil then
|
if packet ~= nil then
|
||||||
-- pass the packet onto the comms message queue
|
-- pass the packet onto the comms message queue
|
||||||
smem.q.mq_comms_rx.push_packet(packet)
|
smem.q.mq_comms_rx.push_packet(packet)
|
||||||
end
|
end
|
||||||
elseif event == "timer" and networked and plc_state.init_ok and conn_watchdog.is_timer(param1) then
|
elseif event == "timer" and networked and conn_watchdog.is_timer(param1) then
|
||||||
-- haven't heard from server recently? close connection and shutdown reactor
|
-- haven't heard from server recently? close connection and shutdown reactor
|
||||||
plc_comms.close()
|
plc_comms.close()
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT)
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT)
|
||||||
@ -146,8 +147,7 @@ function threads.thread__main(smem, init)
|
|||||||
elseif networked and type == "modem" then
|
elseif networked and type == "modem" then
|
||||||
---@cast device Modem
|
---@cast device Modem
|
||||||
-- we only care if this is our wireless modem
|
-- we only care if this is our wireless modem
|
||||||
-- note, check init_ok first since nic will be nil if it is false
|
if nic.is_modem(device) then
|
||||||
if plc_state.init_ok and nic.is_modem(device) then
|
|
||||||
nic.disconnect()
|
nic.disconnect()
|
||||||
|
|
||||||
println_ts("comms modem disconnected!")
|
println_ts("comms modem disconnected!")
|
||||||
@ -161,11 +161,9 @@ function threads.thread__main(smem, init)
|
|||||||
plc_state.no_modem = true
|
plc_state.no_modem = true
|
||||||
plc_state.degraded = true
|
plc_state.degraded = true
|
||||||
|
|
||||||
if plc_state.init_ok then
|
|
||||||
-- try to scram reactor if it is still connected
|
-- try to scram reactor if it is still connected
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.warning("a modem was disconnected")
|
log.warning("a modem was disconnected")
|
||||||
end
|
end
|
||||||
@ -184,7 +182,7 @@ function threads.thread__main(smem, init)
|
|||||||
plc_dev.reactor = device
|
plc_dev.reactor = device
|
||||||
plc_state.no_reactor = false
|
plc_state.no_reactor = false
|
||||||
|
|
||||||
println_ts("reactor reconnected.")
|
println_ts("reactor reconnected")
|
||||||
log.info("reactor reconnected")
|
log.info("reactor reconnected")
|
||||||
|
|
||||||
-- we need to assume formed here as we cannot check in this main loop
|
-- we need to assume formed here as we cannot check in this main loop
|
||||||
@ -196,7 +194,6 @@ function threads.thread__main(smem, init)
|
|||||||
plc_state.degraded = false
|
plc_state.degraded = false
|
||||||
end
|
end
|
||||||
|
|
||||||
if plc_state.init_ok then
|
|
||||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||||
|
|
||||||
rps.reconnect_reactor(plc_dev.reactor)
|
rps.reconnect_reactor(plc_dev.reactor)
|
||||||
@ -206,54 +203,43 @@ function threads.thread__main(smem, init)
|
|||||||
|
|
||||||
-- partial reset of RPS, specific to becoming formed/reconnected
|
-- partial reset of RPS, specific to becoming formed/reconnected
|
||||||
-- without this, auto control can't resume on chunk load
|
-- without this, auto control can't resume on chunk load
|
||||||
rps.reset_formed()
|
rps.reset_reattach()
|
||||||
end
|
|
||||||
elseif networked and type == "modem" then
|
elseif networked and type == "modem" then
|
||||||
---@cast device Modem
|
---@cast device Modem
|
||||||
-- note, check init_ok first since nic will be nil if it is false
|
-- note, check init_ok first since nic will be nil if it is false
|
||||||
if device.isWireless() and not (plc_state.init_ok and nic.is_connected()) then
|
if device.isWireless() and not nic.is_connected() then
|
||||||
-- reconnected modem
|
-- reconnected modem
|
||||||
plc_dev.modem = device
|
plc_dev.modem = device
|
||||||
plc_state.no_modem = false
|
plc_state.no_modem = false
|
||||||
|
|
||||||
if plc_state.init_ok then nic.connect(device) end
|
nic.connect(device)
|
||||||
|
|
||||||
println_ts("wireless modem reconnected.")
|
println_ts("comms modem reconnected")
|
||||||
log.info("comms modem reconnected")
|
log.info("comms modem reconnected")
|
||||||
|
|
||||||
-- determine if we are still in a degraded state
|
-- determine if we are still in a degraded state
|
||||||
if not plc_state.no_reactor then
|
if plc_state.reactor_formed and not plc_state.no_reactor then
|
||||||
plc_state.degraded = false
|
plc_state.degraded = false
|
||||||
end
|
end
|
||||||
elseif device.isWireless() then
|
elseif device.isWireless() then
|
||||||
log.info("unused wireless modem reconnected")
|
log.info("unused wireless modem connected")
|
||||||
else
|
else
|
||||||
log.info("wired modem reconnected")
|
log.info("wired modem connected")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if not init'd and no longer degraded, proceed to init
|
|
||||||
if not plc_state.init_ok and not plc_state.degraded then
|
|
||||||
plc_state.init_ok = true
|
|
||||||
init()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- update indicators
|
-- update indicators
|
||||||
databus.tx_hw_status(plc_state)
|
databus.tx_hw_status(plc_state)
|
||||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or
|
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or
|
||||||
event == "double_click" then
|
event == "double_click" then
|
||||||
-- handle a mouse event
|
-- handle a mouse event
|
||||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||||
elseif event == "clock_start" then
|
|
||||||
-- start loop clock
|
|
||||||
loop_clock.start()
|
|
||||||
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("OS: terminate requested, main thread exiting")
|
||||||
-- rps handles reactor shutdown
|
-- rps handles reactor shutdown
|
||||||
plc_state.shutdown = true
|
plc_state.shutdown = true
|
||||||
break
|
break
|
||||||
@ -277,8 +263,7 @@ function threads.thread__main(smem, init)
|
|||||||
-- if not, we need to restart the clock
|
-- if not, we need to restart the clock
|
||||||
-- this thread cannot be slept because it will miss events (namely "terminate" otherwise)
|
-- this thread cannot be slept because it will miss events (namely "terminate" otherwise)
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("main thread restarting now...")
|
log.info("OS: main thread restarting now...")
|
||||||
util.push_event("clock_start")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -299,7 +284,7 @@ function threads.thread__rps(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("rps", true)
|
databus.tx_rt_status("rps", true)
|
||||||
log.debug("rps thread start")
|
log.debug("OS: rps thread start")
|
||||||
|
|
||||||
-- load in from shared memory
|
-- load in from shared memory
|
||||||
local networked = smem.networked
|
local networked = smem.networked
|
||||||
@ -316,32 +301,25 @@ function threads.thread__rps(smem)
|
|||||||
-- get plc_sys fields (may have been set late due to degraded boot)
|
-- get plc_sys fields (may have been set late due to degraded boot)
|
||||||
local rps = smem.plc_sys.rps
|
local rps = smem.plc_sys.rps
|
||||||
local plc_comms = smem.plc_sys.plc_comms
|
local plc_comms = smem.plc_sys.plc_comms
|
||||||
-- get reactor, may have changed do to disconnect/reconnect
|
-- get reactor, it may have changed due to a disconnect/reconnect
|
||||||
local reactor = plc_dev.reactor
|
local reactor = plc_dev.reactor
|
||||||
|
|
||||||
-- RPS checks
|
|
||||||
if plc_state.init_ok then
|
|
||||||
-- SCRAM if no open connection
|
-- SCRAM if no open connection
|
||||||
if networked and not plc_comms.is_linked() then
|
if networked and not plc_comms.is_linked() then
|
||||||
if was_linked then
|
if was_linked then
|
||||||
was_linked = false
|
was_linked = false
|
||||||
rps.trip_timeout()
|
rps.trip_timeout()
|
||||||
end
|
end
|
||||||
else
|
else was_linked = true end
|
||||||
was_linked = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if (not plc_state.no_reactor) and rps.is_formed() then
|
|
||||||
-- check reactor status
|
-- check reactor status
|
||||||
---@diagnostic disable-next-line: need-check-nil
|
if (not plc_state.no_reactor) and rps.is_formed() then
|
||||||
local reactor_status = reactor.getStatus()
|
local reactor_status = reactor.getStatus()
|
||||||
databus.tx_reactor_state(reactor_status)
|
databus.tx_reactor_state(reactor_status)
|
||||||
|
|
||||||
-- if we tried to SCRAM but failed, keep trying
|
-- if we tried to SCRAM but failed, keep trying
|
||||||
-- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check)
|
-- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check)
|
||||||
if rps.is_tripped() and reactor_status then
|
if rps.is_tripped() and reactor_status then rps.scram() end
|
||||||
rps.scram()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
|
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
|
||||||
@ -349,16 +327,10 @@ function threads.thread__rps(smem)
|
|||||||
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
|
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
|
||||||
|
|
||||||
-- check safety (SCRAM occurs if tripped)
|
-- check safety (SCRAM occurs if tripped)
|
||||||
if not plc_state.no_reactor then
|
local rps_tripped, rps_status_string, rps_first = rps.check(not plc_state.no_reactor)
|
||||||
local rps_tripped, rps_status_string, rps_first = rps.check()
|
|
||||||
|
|
||||||
if rps_tripped and rps_first then
|
if rps_tripped and rps_first then
|
||||||
println_ts("[RPS] SCRAM! safety trip: " .. rps_status_string)
|
println_ts("RPS: SCRAM on safety trip (" .. rps_status_string .. ")")
|
||||||
if networked and not plc_state.no_modem then
|
if networked then plc_comms.send_rps_alarm(rps_status_string) end
|
||||||
plc_comms.send_rps_alarm(rps_status_string)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check for messages in the message queue
|
-- check for messages in the message queue
|
||||||
@ -368,19 +340,19 @@ function threads.thread__rps(smem)
|
|||||||
if msg ~= nil then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- received a command
|
-- received a command
|
||||||
if plc_state.init_ok then
|
|
||||||
if msg.message == MQ__RPS_CMD.SCRAM then
|
if msg.message == MQ__RPS_CMD.SCRAM then
|
||||||
-- SCRAM
|
-- SCRAM
|
||||||
|
log.info("RPS: OS requested SCRAM")
|
||||||
rps.scram()
|
rps.scram()
|
||||||
elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then
|
elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then
|
||||||
-- lost peripheral(s)
|
-- lost peripheral(s)
|
||||||
|
log.info("RPS: received PLC degraded alert")
|
||||||
rps.trip_fault()
|
rps.trip_fault()
|
||||||
elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then
|
elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then
|
||||||
-- watchdog tripped
|
-- watchdog tripped
|
||||||
|
println_ts("RPS: supervisor timeout")
|
||||||
|
log.warning("RPS: received supervisor timeout alert")
|
||||||
rps.trip_timeout()
|
rps.trip_timeout()
|
||||||
println_ts("server timeout")
|
|
||||||
log.warning("server timeout")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
-- received data
|
-- received data
|
||||||
@ -396,17 +368,17 @@ function threads.thread__rps(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("rps thread shutdown initiated")
|
log.info("OS: rps thread shutdown initiated")
|
||||||
if plc_state.init_ok then
|
|
||||||
if rps.scram() then
|
if rps.scram() then
|
||||||
println_ts("reactor disabled")
|
println_ts("exiting, reactor disabled")
|
||||||
log.info("rps thread reactor SCRAM OK")
|
log.info("OS: rps thread reactor SCRAM OK on exit")
|
||||||
else
|
else
|
||||||
println_ts("exiting, reactor failed to disable")
|
println_ts("exiting, reactor failed to disable")
|
||||||
log.error("rps thread failed to SCRAM reactor on exit")
|
log.error("OS: rps thread failed to SCRAM reactor on exit")
|
||||||
end
|
end
|
||||||
end
|
|
||||||
log.info("rps thread exiting")
|
log.info("OS: rps thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -428,8 +400,8 @@ function threads.thread__rps(smem)
|
|||||||
databus.tx_rt_status("rps", false)
|
databus.tx_rt_status("rps", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
if plc_state.init_ok then smem.plc_sys.rps.scram() end
|
smem.plc_sys.rps.scram()
|
||||||
log.info("rps thread restarting in 5 seconds...")
|
log.info("OS: rps thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -448,7 +420,7 @@ function threads.thread__comms_tx(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("comms_tx", true)
|
databus.tx_rt_status("comms_tx", true)
|
||||||
log.debug("comms tx thread start")
|
log.debug("OS: 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
|
||||||
@ -465,7 +437,7 @@ function threads.thread__comms_tx(smem)
|
|||||||
while comms_queue.ready() and not plc_state.shutdown do
|
while comms_queue.ready() and not plc_state.shutdown do
|
||||||
local msg = comms_queue.pop()
|
local msg = comms_queue.pop()
|
||||||
|
|
||||||
if msg ~= nil and plc_state.init_ok then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- received a command
|
-- received a command
|
||||||
if msg.message == MQ__COMM_CMD.SEND_STATUS then
|
if msg.message == MQ__COMM_CMD.SEND_STATUS then
|
||||||
@ -486,7 +458,7 @@ function threads.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("OS: comms tx thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -508,7 +480,7 @@ function threads.thread__comms_tx(smem)
|
|||||||
databus.tx_rt_status("comms_tx", false)
|
databus.tx_rt_status("comms_tx", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("comms tx thread restarting in 5 seconds...")
|
log.info("OS: comms tx thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -521,13 +493,16 @@ end
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param smem plc_shared_memory
|
---@param smem plc_shared_memory
|
||||||
function threads.thread__comms_rx(smem)
|
function threads.thread__comms_rx(smem)
|
||||||
|
-- print a log message to the terminal as long as the UI isn't running
|
||||||
|
local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end
|
||||||
|
|
||||||
---@class parallel_thread
|
---@class parallel_thread
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("comms_rx", true)
|
databus.tx_rt_status("comms_rx", true)
|
||||||
log.debug("comms rx thread start")
|
log.debug("OS: 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
|
||||||
@ -546,7 +521,7 @@ function threads.thread__comms_rx(smem)
|
|||||||
while comms_queue.ready() and not plc_state.shutdown do
|
while comms_queue.ready() and not plc_state.shutdown do
|
||||||
local msg = comms_queue.pop()
|
local msg = comms_queue.pop()
|
||||||
|
|
||||||
if msg ~= nil and plc_state.init_ok then
|
if msg ~= nil then
|
||||||
if msg.qtype == mqueue.TYPE.COMMAND then
|
if msg.qtype == mqueue.TYPE.COMMAND then
|
||||||
-- received a command
|
-- received a command
|
||||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||||
@ -555,7 +530,7 @@ function threads.thread__comms_rx(smem)
|
|||||||
-- received a packet
|
-- received a packet
|
||||||
-- handle the packet (setpoints passed to update burn rate setpoint)
|
-- handle the packet (setpoints passed to update burn rate setpoint)
|
||||||
-- (plc_state passed to check if degraded)
|
-- (plc_state passed to check if degraded)
|
||||||
plc_comms.handle_packet(msg.message, plc_state, setpoints)
|
plc_comms.handle_packet(msg.message, plc_state, setpoints, println_ts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -565,7 +540,7 @@ function threads.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("OS: comms rx thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -587,7 +562,7 @@ function threads.thread__comms_rx(smem)
|
|||||||
databus.tx_rt_status("comms_rx", false)
|
databus.tx_rt_status("comms_rx", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("comms rx thread restarting in 5 seconds...")
|
log.info("OS: comms rx thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -606,7 +581,7 @@ function threads.thread__setpoint_control(smem)
|
|||||||
-- execute thread
|
-- execute thread
|
||||||
function public.exec()
|
function public.exec()
|
||||||
databus.tx_rt_status("spctl", true)
|
databus.tx_rt_status("spctl", true)
|
||||||
log.debug("setpoint control thread start")
|
log.debug("OS: 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
|
||||||
@ -629,9 +604,7 @@ function threads.thread__setpoint_control(smem)
|
|||||||
-- get reactor, may have changed do to disconnect/reconnect
|
-- get reactor, may have changed do to disconnect/reconnect
|
||||||
local reactor = plc_dev.reactor
|
local reactor = plc_dev.reactor
|
||||||
|
|
||||||
if plc_state.init_ok and (not plc_state.no_reactor) then
|
if not plc_state.no_reactor then
|
||||||
---@cast reactor table won't be nil
|
|
||||||
|
|
||||||
-- check if we should start ramping
|
-- check if we should start ramping
|
||||||
if setpoints.burn_rate_en and (setpoints.burn_rate ~= last_burn_sp) then
|
if setpoints.burn_rate_en and (setpoints.burn_rate ~= last_burn_sp) then
|
||||||
local cur_burn_rate = reactor.getBurnRate()
|
local cur_burn_rate = reactor.getBurnRate()
|
||||||
@ -698,7 +671,7 @@ function threads.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("OS: setpoint control thread exiting")
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -720,7 +693,7 @@ function threads.thread__setpoint_control(smem)
|
|||||||
databus.tx_rt_status("spctl", false)
|
databus.tx_rt_status("spctl", false)
|
||||||
|
|
||||||
if not plc_state.shutdown then
|
if not plc_state.shutdown then
|
||||||
log.info("setpoint control thread restarting in 5 seconds...")
|
log.info("OS: setpoint control thread restarting in 5 seconds...")
|
||||||
util.psleep(5)
|
util.psleep(5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -205,7 +205,7 @@ function comms.scada_packet()
|
|||||||
|
|
||||||
if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then
|
if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then
|
||||||
-- outside of maximum allowable transmission distance
|
-- outside of maximum allowable transmission distance
|
||||||
-- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
-- log.debug("COMMS: comms.scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
||||||
else
|
else
|
||||||
if type(self.raw) == "table" then
|
if type(self.raw) == "table" then
|
||||||
if #self.raw == 5 then
|
if #self.raw == 5 then
|
||||||
@ -326,7 +326,7 @@ function comms.authd_packet()
|
|||||||
|
|
||||||
if (type(max_distance) == "number") and ((type(distance) ~= "number") or (distance > max_distance)) then
|
if (type(max_distance) == "number") and ((type(distance) ~= "number") or (distance > max_distance)) then
|
||||||
-- outside of maximum allowable transmission distance
|
-- outside of maximum allowable transmission distance
|
||||||
-- log.debug("comms.authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
-- log.debug("COMMS: comms.authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
|
||||||
else
|
else
|
||||||
if type(self.raw) == "table" then
|
if type(self.raw) == "table" then
|
||||||
if #self.raw == 4 then
|
if #self.raw == 4 then
|
||||||
@ -412,7 +412,7 @@ function comms.modbus_packet()
|
|||||||
self.raw = { self.txn_id, self.unit_id, self.func_code }
|
self.raw = { self.txn_id, self.unit_id, self.func_code }
|
||||||
for i = 1, self.length do insert(self.raw, data[i]) end
|
for i = 1, self.length do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.modbus_packet.make(): data not a table")
|
log.error("COMMS: modbus_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -435,11 +435,11 @@ function comms.modbus_packet()
|
|||||||
|
|
||||||
return size_ok and valid
|
return size_ok and valid
|
||||||
else
|
else
|
||||||
log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: 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("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -498,7 +498,7 @@ function comms.rplc_packet()
|
|||||||
self.raw = { self.id, self.type }
|
self.raw = { self.id, self.type }
|
||||||
for i = 1, #data do insert(self.raw, data[i]) end
|
for i = 1, #data do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.rplc_packet.make(): data not a table")
|
log.error("COMMS: rplc_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -521,11 +521,11 @@ function comms.rplc_packet()
|
|||||||
|
|
||||||
return ok
|
return ok
|
||||||
else
|
else
|
||||||
log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: 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("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -580,7 +580,7 @@ function comms.mgmt_packet()
|
|||||||
self.raw = { self.type }
|
self.raw = { self.type }
|
||||||
for i = 1, #data do insert(self.raw, data[i]) end
|
for i = 1, #data do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.mgmt_packet.make(): data not a table")
|
log.error("COMMS: mgmt_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -601,11 +601,11 @@ function comms.mgmt_packet()
|
|||||||
|
|
||||||
return ok
|
return ok
|
||||||
else
|
else
|
||||||
log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: 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("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -659,7 +659,7 @@ function comms.crdn_packet()
|
|||||||
self.raw = { self.type }
|
self.raw = { self.type }
|
||||||
for i = 1, #data do insert(self.raw, data[i]) end
|
for i = 1, #data do insert(self.raw, data[i]) end
|
||||||
else
|
else
|
||||||
log.error("comms.crdn_packet.make(): data not a table")
|
log.error("COMMS: crdn_packet.make(): data not a table")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -680,11 +680,11 @@ function comms.crdn_packet()
|
|||||||
|
|
||||||
return ok
|
return ok
|
||||||
else
|
else
|
||||||
log.debug("attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
|
log.debug("COMMS: attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("nil frame encountered", true)
|
log.debug("COMMS: nil frame encountered", true)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -20,7 +20,7 @@ local MODE = { APPEND = 0, NEW = 1 }
|
|||||||
|
|
||||||
log.MODE = MODE
|
log.MODE = MODE
|
||||||
|
|
||||||
local logger = {
|
local _log = {
|
||||||
not_ready = true,
|
not_ready = true,
|
||||||
path = "/log.txt",
|
path = "/log.txt",
|
||||||
mode = MODE.APPEND,
|
mode = MODE.APPEND,
|
||||||
@ -42,36 +42,36 @@ local free_space = fs.getFreeSpace
|
|||||||
---@param err_msg string|nil error message
|
---@param err_msg string|nil error message
|
||||||
---@return boolean out_of_space
|
---@return boolean out_of_space
|
||||||
local function check_out_of_space(err_msg)
|
local function check_out_of_space(err_msg)
|
||||||
return (free_space(logger.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
|
return (free_space(_log.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- private log write function
|
-- private log write function
|
||||||
---@param msg_bits any[]
|
---@param msg_bits any[]
|
||||||
local function _log(msg_bits)
|
local function write_log(msg_bits)
|
||||||
if logger.not_ready then return end
|
if _log.not_ready then return end
|
||||||
|
|
||||||
local time_stamp = os.date(TIME_FMT)
|
local time_stamp = os.date(TIME_FMT)
|
||||||
local stamped = util.c(time_stamp, table.unpack(msg_bits))
|
local stamped = util.c(time_stamp, table.unpack(msg_bits))
|
||||||
|
|
||||||
-- attempt to write log
|
-- attempt to write log
|
||||||
local status, result = pcall(function ()
|
local status, result = pcall(function ()
|
||||||
logger.file.writeLine(stamped)
|
_log.file.writeLine(stamped)
|
||||||
logger.file.flush()
|
_log.file.flush()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- if we don't have space, we need to create a new log file
|
-- if we don't have space, we need to create a new log file
|
||||||
if check_out_of_space() then
|
if check_out_of_space() then
|
||||||
-- delete the old log file before opening a new one
|
-- delete the old log file before opening a new one
|
||||||
logger.file.close()
|
_log.file.close()
|
||||||
fs.delete(logger.path)
|
fs.delete(_log.path)
|
||||||
|
|
||||||
-- re-init logger and pass dmesg_out so that it doesn't change
|
-- re-init logger and pass dmesg_out so that it doesn't change
|
||||||
log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out)
|
log.init(_log.path, _log.mode, _log.debug, _log.dmesg_out)
|
||||||
|
|
||||||
-- log the message and recycle warning
|
-- log the message and recycle warning
|
||||||
logger.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
|
_log.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
|
||||||
logger.file.writeLine(stamped)
|
_log.file.writeLine(stamped)
|
||||||
logger.file.flush()
|
_log.file.flush()
|
||||||
elseif (not status) and (result ~= nil) then
|
elseif (not status) and (result ~= nil) then
|
||||||
util.println("unexpected error writing to the log file: " .. result)
|
util.println("unexpected error writing to the log file: " .. result)
|
||||||
end
|
end
|
||||||
@ -89,45 +89,45 @@ end
|
|||||||
function log.init(path, write_mode, include_debug, dmesg_redirect)
|
function log.init(path, write_mode, include_debug, dmesg_redirect)
|
||||||
local err_msg
|
local err_msg
|
||||||
|
|
||||||
logger.path = path
|
_log.path = path
|
||||||
logger.mode = write_mode
|
_log.mode = write_mode
|
||||||
logger.debug = include_debug
|
_log.debug = include_debug
|
||||||
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
_log.file, err_msg = fs.open(path, util.trinary(_log.mode == MODE.APPEND, "a", "w"))
|
||||||
|
|
||||||
if dmesg_redirect then
|
if dmesg_redirect then
|
||||||
logger.dmesg_out = dmesg_redirect
|
_log.dmesg_out = dmesg_redirect
|
||||||
else
|
else
|
||||||
logger.dmesg_out = term.current()
|
_log.dmesg_out = term.current()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check for space issues
|
-- check for space issues
|
||||||
local out_of_space = check_out_of_space(err_msg)
|
local out_of_space = check_out_of_space(err_msg)
|
||||||
|
|
||||||
-- try to handle problems
|
-- try to handle problems
|
||||||
if logger.file == nil or out_of_space then
|
if _log.file == nil or out_of_space then
|
||||||
if out_of_space then
|
if out_of_space then
|
||||||
if fs.exists(logger.path) then
|
if fs.exists(_log.path) then
|
||||||
fs.delete(logger.path)
|
fs.delete(_log.path)
|
||||||
|
|
||||||
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
_log.file, err_msg = fs.open(path, util.trinary(_log.mode == MODE.APPEND, "a", "w"))
|
||||||
|
|
||||||
if logger.file then
|
if _log.file then
|
||||||
logger.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
|
_log.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
|
||||||
logger.file.flush()
|
_log.file.flush()
|
||||||
else error("failed to setup the log file: " .. err_msg) end
|
else error("failed to setup the log file: " .. err_msg) end
|
||||||
else error("failed to make space for the log file, please delete unused files") end
|
else error("failed to make space for the log file, please delete unused files") end
|
||||||
else error("unexpected error setting up the log file: " .. err_msg) end
|
else error("unexpected error setting up the log file: " .. err_msg) end
|
||||||
end
|
end
|
||||||
|
|
||||||
logger.not_ready = false
|
_log.not_ready = false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- close the log file handle
|
-- close the log file handle
|
||||||
function log.close() logger.file.close() end
|
function log.close() _log.file.close() end
|
||||||
|
|
||||||
-- direct dmesg output to a monitor/window
|
-- direct dmesg output to a monitor/window
|
||||||
---@param window Window window or terminal reference
|
---@param window Window window or terminal reference
|
||||||
function log.direct_dmesg(window) logger.dmesg_out = window end
|
function log.direct_dmesg(window) _log.dmesg_out = window end
|
||||||
|
|
||||||
-- dmesg style logging for boot because I like linux-y things
|
-- dmesg style logging for boot because I like linux-y things
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
@ -142,7 +142,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
tag = util.strval(tag or "")
|
tag = util.strval(tag or "")
|
||||||
|
|
||||||
local t_stamp = string.format("%12.2f", os.clock())
|
local t_stamp = string.format("%12.2f", os.clock())
|
||||||
local out = logger.dmesg_out
|
local out = _log.dmesg_out
|
||||||
|
|
||||||
if out ~= nil then
|
if out ~= nil then
|
||||||
local out_w, out_h = out.getSize()
|
local out_w, out_h = out.getSize()
|
||||||
@ -180,7 +180,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
if cur_y == out_h then
|
if cur_y == out_h then
|
||||||
out.scroll(1)
|
out.scroll(1)
|
||||||
out.setCursorPos(1, cur_y)
|
out.setCursorPos(1, cur_y)
|
||||||
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
|
_log.dmesg_scroll_count = _log.dmesg_scroll_count + 1
|
||||||
else
|
else
|
||||||
out.setCursorPos(1, cur_y + 1)
|
out.setCursorPos(1, cur_y + 1)
|
||||||
end
|
end
|
||||||
@ -216,7 +216,7 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
if cur_y == out_h then
|
if cur_y == out_h then
|
||||||
out.scroll(1)
|
out.scroll(1)
|
||||||
out.setCursorPos(1, cur_y)
|
out.setCursorPos(1, cur_y)
|
||||||
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
|
_log.dmesg_scroll_count = _log.dmesg_scroll_count + 1
|
||||||
else
|
else
|
||||||
out.setCursorPos(1, cur_y + 1)
|
out.setCursorPos(1, cur_y + 1)
|
||||||
end
|
end
|
||||||
@ -225,9 +225,9 @@ function log.dmesg(msg, tag, tag_color)
|
|||||||
out.write(lines[i])
|
out.write(lines[i])
|
||||||
end
|
end
|
||||||
|
|
||||||
logger.dmesg_restore_coord = { out.getCursorPos() }
|
_log.dmesg_restore_coord = { out.getCursorPos() }
|
||||||
|
|
||||||
_log{"[", t_stamp, "] [", tag, "] ", msg}
|
write_log{"[", t_stamp, "] [", tag, "] ", msg}
|
||||||
end
|
end
|
||||||
|
|
||||||
return ts_coord
|
return ts_coord
|
||||||
@ -241,9 +241,9 @@ end
|
|||||||
---@return function update, function done
|
---@return function update, function done
|
||||||
function log.dmesg_working(msg, tag, tag_color)
|
function log.dmesg_working(msg, tag, tag_color)
|
||||||
local ts_coord = log.dmesg(msg, tag, tag_color)
|
local ts_coord = log.dmesg(msg, tag, tag_color)
|
||||||
local initial_scroll = logger.dmesg_scroll_count
|
local initial_scroll = _log.dmesg_scroll_count
|
||||||
|
|
||||||
local out = logger.dmesg_out
|
local out = _log.dmesg_out
|
||||||
local width = (ts_coord.x2 - ts_coord.x1) + 1
|
local width = (ts_coord.x2 - ts_coord.x1) + 1
|
||||||
|
|
||||||
if out ~= nil then
|
if out ~= nil then
|
||||||
@ -252,7 +252,7 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
local counter = 0
|
local counter = 0
|
||||||
|
|
||||||
local function update(sec_remaining)
|
local function update(sec_remaining)
|
||||||
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
|
local new_y = ts_coord.y - (_log.dmesg_scroll_count - initial_scroll)
|
||||||
if new_y < 1 then return end
|
if new_y < 1 then return end
|
||||||
|
|
||||||
local time = util.sprintf("%ds", sec_remaining)
|
local time = util.sprintf("%ds", sec_remaining)
|
||||||
@ -280,11 +280,11 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
|
|
||||||
counter = counter + 1
|
counter = counter + 1
|
||||||
|
|
||||||
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
|
out.setCursorPos(table.unpack(_log.dmesg_restore_coord))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function done(ok)
|
local function done(ok)
|
||||||
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
|
local new_y = ts_coord.y - (_log.dmesg_scroll_count - initial_scroll)
|
||||||
if new_y < 1 then return end
|
if new_y < 1 then return end
|
||||||
|
|
||||||
out.setCursorPos(ts_coord.x1, new_y)
|
out.setCursorPos(ts_coord.x1, new_y)
|
||||||
@ -299,7 +299,7 @@ function log.dmesg_working(msg, tag, tag_color)
|
|||||||
|
|
||||||
out.setTextColor(initial_color)
|
out.setTextColor(initial_color)
|
||||||
|
|
||||||
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
|
out.setCursorPos(table.unpack(_log.dmesg_restore_coord))
|
||||||
end
|
end
|
||||||
|
|
||||||
return update, done
|
return update, done
|
||||||
@ -312,28 +312,28 @@ end
|
|||||||
---@param msg any message
|
---@param msg any message
|
||||||
---@param trace? boolean include file trace
|
---@param trace? boolean include file trace
|
||||||
function log.debug(msg, trace)
|
function log.debug(msg, trace)
|
||||||
if logger.debug then
|
if _log.debug then
|
||||||
if trace then
|
if trace then
|
||||||
local info = debug.getinfo(2)
|
local info = debug.getinfo(2)
|
||||||
|
|
||||||
if info.name ~= nil then
|
if info.name ~= nil then
|
||||||
_log{DBG_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
write_log{DBG_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
||||||
else
|
else
|
||||||
_log{DBG_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
write_log{DBG_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_log{DBG_TAG, msg}
|
write_log{DBG_TAG, msg}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log info messages
|
-- log info messages
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
function log.info(msg) _log{INF_TAG, msg} end
|
function log.info(msg) write_log{INF_TAG, msg} end
|
||||||
|
|
||||||
-- log warning messages
|
-- log warning messages
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
function log.warning(msg) _log{WRN_TAG, msg} end
|
function log.warning(msg) write_log{WRN_TAG, msg} end
|
||||||
|
|
||||||
-- log error messages
|
-- log error messages
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
@ -343,17 +343,17 @@ function log.error(msg, trace)
|
|||||||
local info = debug.getinfo(2)
|
local info = debug.getinfo(2)
|
||||||
|
|
||||||
if info.name ~= nil then
|
if info.name ~= nil then
|
||||||
_log{ERR_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
write_log{ERR_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
|
||||||
else
|
else
|
||||||
_log{ERR_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
write_log{ERR_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
_log{ERR_TAG, msg}
|
write_log{ERR_TAG, msg}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log fatal errors
|
-- log fatal errors
|
||||||
---@param msg any message
|
---@param msg any message
|
||||||
function log.fatal(msg) _log{FTL_TAG, msg} end
|
function log.fatal(msg) write_log{FTL_TAG, msg} end
|
||||||
|
|
||||||
return log
|
return log
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
--
|
--
|
||||||
-- Network Communications
|
-- Network Communications and Message Authentication
|
||||||
--
|
--
|
||||||
|
|
||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
|
local ppm = require("scada-common.ppm")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local md5 = require("lockbox.digest.md5")
|
local md5 = require("lockbox.digest.md5")
|
||||||
@ -17,7 +18,7 @@ local array = require("lockbox.util.array")
|
|||||||
local network = {}
|
local network = {}
|
||||||
|
|
||||||
-- cryptography engine
|
-- cryptography engine
|
||||||
local c_eng = {
|
local _crypt = {
|
||||||
key = nil,
|
key = nil,
|
||||||
hmac = nil
|
hmac = nil
|
||||||
}
|
}
|
||||||
@ -39,23 +40,23 @@ function network.init_mac(passkey)
|
|||||||
key_deriv.setPassword(passkey)
|
key_deriv.setPassword(passkey)
|
||||||
key_deriv.finish()
|
key_deriv.finish()
|
||||||
|
|
||||||
c_eng.key = array.fromHex(key_deriv.asHex())
|
_crypt.key = array.fromHex(key_deriv.asHex())
|
||||||
|
|
||||||
-- initialize HMAC
|
-- initialize HMAC
|
||||||
c_eng.hmac = hmac()
|
_crypt.hmac = hmac()
|
||||||
c_eng.hmac.setBlockSize(64)
|
_crypt.hmac.setBlockSize(64)
|
||||||
c_eng.hmac.setDigest(md5)
|
_crypt.hmac.setDigest(md5)
|
||||||
c_eng.hmac.setKey(c_eng.key)
|
_crypt.hmac.setKey(_crypt.key)
|
||||||
|
|
||||||
local init_time = util.time_ms() - start
|
local init_time = util.time_ms() - start
|
||||||
log.info("network.init_mac completed in " .. init_time .. "ms")
|
log.info("NET: network.init_mac completed in " .. init_time .. "ms")
|
||||||
|
|
||||||
return init_time
|
return init_time
|
||||||
end
|
end
|
||||||
|
|
||||||
-- de-initialize message authentication system
|
-- de-initialize message authentication system
|
||||||
function network.deinit_mac()
|
function network.deinit_mac()
|
||||||
c_eng.key, c_eng.hmac = nil, nil
|
_crypt.key, _crypt.hmac = nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- generate HMAC of message
|
-- generate HMAC of message
|
||||||
@ -64,29 +65,41 @@ end
|
|||||||
local function compute_hmac(message)
|
local function compute_hmac(message)
|
||||||
-- local start = util.time_ms()
|
-- local start = util.time_ms()
|
||||||
|
|
||||||
c_eng.hmac.init()
|
_crypt.hmac.init()
|
||||||
c_eng.hmac.update(stream.fromString(message))
|
_crypt.hmac.update(stream.fromString(message))
|
||||||
c_eng.hmac.finish()
|
_crypt.hmac.finish()
|
||||||
|
|
||||||
local hash = c_eng.hmac.asHex()
|
local hash = _crypt.hmac.asHex()
|
||||||
|
|
||||||
-- log.debug("compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)")
|
-- log.debug("NET: compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)")
|
||||||
|
|
||||||
return hash
|
return hash
|
||||||
end
|
end
|
||||||
|
|
||||||
-- NIC: Network Interface Controller<br>
|
-- NIC: Network Interface Controller<br>
|
||||||
-- utilizes HMAC-MD5 for message authentication, if enabled
|
-- utilizes HMAC-MD5 for message authentication, if enabled and this is wireless
|
||||||
---@param modem Modem modem to use
|
---@param modem Modem|nil modem to use
|
||||||
function network.nic(modem)
|
function network.nic(modem)
|
||||||
local self = {
|
local self = {
|
||||||
connected = true, -- used to avoid costly MAC calculations if modem isn't even present
|
-- modem interface name
|
||||||
|
iface = "?",
|
||||||
|
-- phy name
|
||||||
|
name = "?",
|
||||||
|
-- used to quickly return out of tx/rx functions if there is nothing to do
|
||||||
|
connected = false,
|
||||||
|
-- used to avoid costly MAC calculations if not required
|
||||||
|
use_hash = false,
|
||||||
|
-- open channels
|
||||||
channels = {}
|
channels = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class nic:Modem
|
---@class nic:Modem
|
||||||
local public = {}
|
local public = {}
|
||||||
|
|
||||||
|
-- get the phy name
|
||||||
|
---@nodiscard
|
||||||
|
function public.phy_name() return self.name end
|
||||||
|
|
||||||
-- check if this NIC has a connected modem
|
-- check if this NIC has a connected modem
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.is_connected() return self.connected end
|
function public.is_connected() return self.connected end
|
||||||
@ -95,9 +108,14 @@ function network.nic(modem)
|
|||||||
---@param reconnected_modem Modem
|
---@param reconnected_modem Modem
|
||||||
function public.connect(reconnected_modem)
|
function public.connect(reconnected_modem)
|
||||||
modem = reconnected_modem
|
modem = reconnected_modem
|
||||||
self.connected = true
|
|
||||||
|
|
||||||
-- open previously opened channels
|
self.iface = ppm.get_iface(modem)
|
||||||
|
self.name = util.c(util.trinary(modem.isWireless(), "WLAN_PHY", "ETH_PHY"), "{", self.iface, "}")
|
||||||
|
self.connected = true
|
||||||
|
self.use_hash = _crypt.hmac and modem.isWireless()
|
||||||
|
|
||||||
|
-- open only previously opened channels
|
||||||
|
modem.closeAll()
|
||||||
for _, channel in ipairs(self.channels) do
|
for _, channel in ipairs(self.channels) do
|
||||||
modem.open(channel)
|
modem.open(channel)
|
||||||
end
|
end
|
||||||
@ -117,13 +135,13 @@ function network.nic(modem)
|
|||||||
function public.is_modem(device) return device == modem end
|
function public.is_modem(device) return device == modem end
|
||||||
|
|
||||||
-- wrap modem functions, then create custom functions
|
-- wrap modem functions, then create custom functions
|
||||||
public.connect(modem)
|
if modem then public.connect(modem) end
|
||||||
|
|
||||||
-- open a channel on the modem<br>
|
-- open a channel on the modem<br>
|
||||||
-- if disconnected *after* opening, previousy opened channels will be re-opened on reconnection
|
-- if disconnected *after* opening, previousy opened channels will be re-opened on reconnection
|
||||||
---@param channel integer
|
---@param channel integer
|
||||||
function public.open(channel)
|
function public.open(channel)
|
||||||
modem.open(channel)
|
if modem then modem.open(channel) end
|
||||||
|
|
||||||
local already_open = false
|
local already_open = false
|
||||||
for i = 1, #self.channels do
|
for i = 1, #self.channels do
|
||||||
@ -141,7 +159,7 @@ function network.nic(modem)
|
|||||||
-- close a channel on the modem
|
-- close a channel on the modem
|
||||||
---@param channel integer
|
---@param channel integer
|
||||||
function public.close(channel)
|
function public.close(channel)
|
||||||
modem.close(channel)
|
if modem then modem.close(channel) end
|
||||||
|
|
||||||
for i = 1, #self.channels do
|
for i = 1, #self.channels do
|
||||||
if self.channels[i] == channel then
|
if self.channels[i] == channel then
|
||||||
@ -153,7 +171,7 @@ function network.nic(modem)
|
|||||||
|
|
||||||
-- close all channels on the modem
|
-- close all channels on the modem
|
||||||
function public.closeAll()
|
function public.closeAll()
|
||||||
modem.closeAll()
|
if modem then modem.closeAll() end
|
||||||
self.channels = {}
|
self.channels = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -165,17 +183,20 @@ function network.nic(modem)
|
|||||||
if self.connected then
|
if self.connected then
|
||||||
local tx_packet = packet ---@type authd_packet|scada_packet
|
local tx_packet = packet ---@type authd_packet|scada_packet
|
||||||
|
|
||||||
if c_eng.hmac ~= nil then
|
if self.use_hash then
|
||||||
-- local start = util.time_ms()
|
-- local start = util.time_ms()
|
||||||
tx_packet = comms.authd_packet()
|
tx_packet = comms.authd_packet()
|
||||||
|
|
||||||
---@cast tx_packet authd_packet
|
---@cast tx_packet authd_packet
|
||||||
tx_packet.make(packet, compute_hmac)
|
tx_packet.make(packet, compute_hmac)
|
||||||
|
|
||||||
-- log.debug("network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("NET: network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line: need-check-nil
|
||||||
modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
|
modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
|
||||||
|
else
|
||||||
|
log.debug("NET: network.transmit tx dropped, link is down")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -190,10 +211,10 @@ function network.nic(modem)
|
|||||||
function public.receive(side, sender, reply_to, message, distance)
|
function public.receive(side, sender, reply_to, message, distance)
|
||||||
local packet = nil
|
local packet = nil
|
||||||
|
|
||||||
if self.connected then
|
if self.connected and side == self.iface then
|
||||||
local s_packet = comms.scada_packet()
|
local s_packet = comms.scada_packet()
|
||||||
|
|
||||||
if c_eng.hmac ~= nil then
|
if self.use_hash then
|
||||||
-- parse packet as an authenticated SCADA packet
|
-- parse packet as an authenticated SCADA packet
|
||||||
local a_packet = comms.authd_packet()
|
local a_packet = comms.authd_packet()
|
||||||
a_packet.receive(side, sender, reply_to, message, distance)
|
a_packet.receive(side, sender, reply_to, message, distance)
|
||||||
@ -206,10 +227,10 @@ function network.nic(modem)
|
|||||||
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
|
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
|
||||||
|
|
||||||
if a_packet.mac() == computed_hmac then
|
if a_packet.mac() == computed_hmac then
|
||||||
-- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("NET: network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
||||||
s_packet.stamp_authenticated()
|
s_packet.stamp_authenticated()
|
||||||
else
|
else
|
||||||
-- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
-- log.debug("NET: network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -22,7 +22,7 @@ ppm.VIRTUAL_DEVICE_TYPE = VIRTUAL_DEVICE_TYPE
|
|||||||
|
|
||||||
local REPORT_FREQUENCY = 20 -- log every 20 faults per function
|
local REPORT_FREQUENCY = 20 -- log every 20 faults per function
|
||||||
|
|
||||||
local ppm_sys = {
|
local _ppm = {
|
||||||
mounts = {}, ---@type { [string]: ppm_entry }
|
mounts = {}, ---@type { [string]: ppm_entry }
|
||||||
next_vid = 0,
|
next_vid = 0,
|
||||||
auto_cf = false,
|
auto_cf = false,
|
||||||
@ -66,7 +66,7 @@ local function peri_init(iface)
|
|||||||
if status then
|
if status then
|
||||||
-- auto fault clear
|
-- auto fault clear
|
||||||
if self.auto_cf then self.faulted = false end
|
if self.auto_cf then self.faulted = false end
|
||||||
if ppm_sys.auto_cf then ppm_sys.faulted = false end
|
if _ppm.auto_cf then _ppm.faulted = false end
|
||||||
|
|
||||||
self.fault_counts[key] = 0
|
self.fault_counts[key] = 0
|
||||||
|
|
||||||
@ -78,10 +78,10 @@ local function peri_init(iface)
|
|||||||
self.faulted = true
|
self.faulted = true
|
||||||
self.last_fault = result
|
self.last_fault = result
|
||||||
|
|
||||||
ppm_sys.faulted = true
|
_ppm.faulted = true
|
||||||
ppm_sys.last_fault = result
|
_ppm.last_fault = result
|
||||||
|
|
||||||
if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
if not _ppm.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
||||||
local count_str = ""
|
local count_str = ""
|
||||||
if self.fault_counts[key] > 0 then
|
if self.fault_counts[key] > 0 then
|
||||||
count_str = " [" .. self.fault_counts[key] .. " total faults]"
|
count_str = " [" .. self.fault_counts[key] .. " total faults]"
|
||||||
@ -92,7 +92,7 @@ local function peri_init(iface)
|
|||||||
|
|
||||||
self.fault_counts[key] = self.fault_counts[key] + 1
|
self.fault_counts[key] = self.fault_counts[key] + 1
|
||||||
|
|
||||||
if result == "Terminated" then ppm_sys.terminate = true end
|
if result == "Terminated" then _ppm.terminate = true end
|
||||||
|
|
||||||
return ACCESS_FAULT, result
|
return ACCESS_FAULT, result
|
||||||
end
|
end
|
||||||
@ -159,10 +159,10 @@ local function peri_init(iface)
|
|||||||
self.faulted = true
|
self.faulted = true
|
||||||
self.last_fault = UNDEFINED_FIELD
|
self.last_fault = UNDEFINED_FIELD
|
||||||
|
|
||||||
ppm_sys.faulted = true
|
_ppm.faulted = true
|
||||||
ppm_sys.last_fault = UNDEFINED_FIELD
|
_ppm.last_fault = UNDEFINED_FIELD
|
||||||
|
|
||||||
if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
if not _ppm.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
|
||||||
local count_str = ""
|
local count_str = ""
|
||||||
if self.fault_counts[key] > 0 then
|
if self.fault_counts[key] > 0 then
|
||||||
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
count_str = " [" .. self.fault_counts[key] .. " total calls]"
|
||||||
@ -193,35 +193,35 @@ end
|
|||||||
-- REPORTING --
|
-- REPORTING --
|
||||||
|
|
||||||
-- silence error prints
|
-- silence error prints
|
||||||
function ppm.disable_reporting() ppm_sys.mute = true end
|
function ppm.disable_reporting() _ppm.mute = true end
|
||||||
|
|
||||||
-- allow error prints
|
-- allow error prints
|
||||||
function ppm.enable_reporting() ppm_sys.mute = false end
|
function ppm.enable_reporting() _ppm.mute = false end
|
||||||
|
|
||||||
-- FAULT MEMORY --
|
-- FAULT MEMORY --
|
||||||
|
|
||||||
-- enable automatically clearing fault flag
|
-- enable automatically clearing fault flag
|
||||||
function ppm.enable_afc() ppm_sys.auto_cf = true end
|
function ppm.enable_afc() _ppm.auto_cf = true end
|
||||||
|
|
||||||
-- disable automatically clearing fault flag
|
-- disable automatically clearing fault flag
|
||||||
function ppm.disable_afc() ppm_sys.auto_cf = false end
|
function ppm.disable_afc() _ppm.auto_cf = false end
|
||||||
|
|
||||||
-- clear fault flag
|
-- clear fault flag
|
||||||
function ppm.clear_fault() ppm_sys.faulted = false end
|
function ppm.clear_fault() _ppm.faulted = false end
|
||||||
|
|
||||||
-- check fault flag
|
-- check fault flag
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function ppm.is_faulted() return ppm_sys.faulted end
|
function ppm.is_faulted() return _ppm.faulted end
|
||||||
|
|
||||||
-- get the last fault message
|
-- get the last fault message
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function ppm.get_last_fault() return ppm_sys.last_fault end
|
function ppm.get_last_fault() return _ppm.last_fault end
|
||||||
|
|
||||||
-- TERMINATION --
|
-- TERMINATION --
|
||||||
|
|
||||||
-- if a caught error was a termination request
|
-- if a caught error was a termination request
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function ppm.should_terminate() return ppm_sys.terminate end
|
function ppm.should_terminate() return _ppm.terminate end
|
||||||
|
|
||||||
-- MOUNTING --
|
-- MOUNTING --
|
||||||
|
|
||||||
@ -229,12 +229,12 @@ function ppm.should_terminate() return ppm_sys.terminate end
|
|||||||
function ppm.mount_all()
|
function ppm.mount_all()
|
||||||
local ifaces = peripheral.getNames()
|
local ifaces = peripheral.getNames()
|
||||||
|
|
||||||
ppm_sys.mounts = {}
|
_ppm.mounts = {}
|
||||||
|
|
||||||
for i = 1, #ifaces do
|
for i = 1, #ifaces do
|
||||||
ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i])
|
_ppm.mounts[ifaces[i]] = peri_init(ifaces[i])
|
||||||
|
|
||||||
log.info(util.c("PPM: found a ", ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")"))
|
log.info(util.c("PPM: found a ", _ppm.mounts[ifaces[i]].type, " (", ifaces[i], ")"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if #ifaces == 0 then
|
if #ifaces == 0 then
|
||||||
@ -253,10 +253,10 @@ function ppm.mount(iface)
|
|||||||
|
|
||||||
for i = 1, #ifaces do
|
for i = 1, #ifaces do
|
||||||
if iface == ifaces[i] then
|
if iface == ifaces[i] then
|
||||||
ppm_sys.mounts[iface] = peri_init(iface)
|
_ppm.mounts[iface] = peri_init(iface)
|
||||||
|
|
||||||
pm_type = ppm_sys.mounts[iface].type
|
pm_type = _ppm.mounts[iface].type
|
||||||
pm_dev = ppm_sys.mounts[iface].dev
|
pm_dev = _ppm.mounts[iface].dev
|
||||||
|
|
||||||
log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type))
|
log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type))
|
||||||
break
|
break
|
||||||
@ -278,12 +278,12 @@ function ppm.remount(iface)
|
|||||||
for i = 1, #ifaces do
|
for i = 1, #ifaces do
|
||||||
if iface == ifaces[i] then
|
if iface == ifaces[i] then
|
||||||
log.info(util.c("PPM: remount(", iface, ") -> is a ", pm_type))
|
log.info(util.c("PPM: remount(", iface, ") -> is a ", pm_type))
|
||||||
ppm.unmount(ppm_sys.mounts[iface].dev)
|
ppm.unmount(_ppm.mounts[iface].dev)
|
||||||
|
|
||||||
ppm_sys.mounts[iface] = peri_init(iface)
|
_ppm.mounts[iface] = peri_init(iface)
|
||||||
|
|
||||||
pm_type = ppm_sys.mounts[iface].type
|
pm_type = _ppm.mounts[iface].type
|
||||||
pm_dev = ppm_sys.mounts[iface].dev
|
pm_dev = _ppm.mounts[iface].dev
|
||||||
|
|
||||||
log.info(util.c("PPM: remount(", iface, ") -> remounted a ", pm_type))
|
log.info(util.c("PPM: remount(", iface, ") -> remounted a ", pm_type))
|
||||||
break
|
break
|
||||||
@ -297,24 +297,24 @@ end
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@return string type, table device
|
---@return string type, table device
|
||||||
function ppm.mount_virtual()
|
function ppm.mount_virtual()
|
||||||
local iface = "ppm_vdev_" .. ppm_sys.next_vid
|
local iface = "ppm_vdev_" .. _ppm.next_vid
|
||||||
|
|
||||||
ppm_sys.mounts[iface] = peri_init("__virtual__")
|
_ppm.mounts[iface] = peri_init("__virtual__")
|
||||||
ppm_sys.next_vid = ppm_sys.next_vid + 1
|
_ppm.next_vid = _ppm.next_vid + 1
|
||||||
|
|
||||||
log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface))
|
log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface))
|
||||||
|
|
||||||
return ppm_sys.mounts[iface].type, ppm_sys.mounts[iface].dev
|
return _ppm.mounts[iface].type, _ppm.mounts[iface].dev
|
||||||
end
|
end
|
||||||
|
|
||||||
-- manually unmount a peripheral from the PPM
|
-- manually unmount a peripheral from the PPM
|
||||||
---@param device table device table
|
---@param device table device table
|
||||||
function ppm.unmount(device)
|
function ppm.unmount(device)
|
||||||
if device then
|
if device then
|
||||||
for iface, data in pairs(ppm_sys.mounts) do
|
for iface, data in pairs(_ppm.mounts) do
|
||||||
if data.dev == device then
|
if data.dev == device then
|
||||||
log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", iface))
|
log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", iface))
|
||||||
ppm_sys.mounts[iface] = nil
|
_ppm.mounts[iface] = nil
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -330,7 +330,7 @@ function ppm.handle_unmount(iface)
|
|||||||
local pm_type = nil
|
local pm_type = nil
|
||||||
|
|
||||||
-- what got disconnected?
|
-- what got disconnected?
|
||||||
local lost_dev = ppm_sys.mounts[iface]
|
local lost_dev = _ppm.mounts[iface]
|
||||||
|
|
||||||
if lost_dev then
|
if lost_dev then
|
||||||
pm_type = lost_dev.type
|
pm_type = lost_dev.type
|
||||||
@ -341,18 +341,18 @@ function ppm.handle_unmount(iface)
|
|||||||
log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface))
|
log.error(util.c("PPM: lost device unknown to the PPM mounted to ", iface))
|
||||||
end
|
end
|
||||||
|
|
||||||
ppm_sys.mounts[iface] = nil
|
_ppm.mounts[iface] = nil
|
||||||
|
|
||||||
return pm_type, pm_dev
|
return pm_type, pm_dev
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready
|
-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready
|
||||||
function ppm.log_mounts()
|
function ppm.log_mounts()
|
||||||
for iface, mount in pairs(ppm_sys.mounts) do
|
for iface, mount in pairs(_ppm.mounts) do
|
||||||
log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")"))
|
log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if util.table_len(ppm_sys.mounts) == 0 then
|
if util.table_len(_ppm.mounts) == 0 then
|
||||||
log.warning("PPM: no devices had been found")
|
log.warning("PPM: no devices had been found")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -369,7 +369,7 @@ function ppm.list_avail() return peripheral.getNames() end
|
|||||||
---@return { [string]: ppm_entry } mounts
|
---@return { [string]: ppm_entry } mounts
|
||||||
function ppm.list_mounts()
|
function ppm.list_mounts()
|
||||||
local list = {}
|
local list = {}
|
||||||
for k, v in pairs(ppm_sys.mounts) do list[k] = v end
|
for k, v in pairs(_ppm.mounts) do list[k] = v end
|
||||||
return list
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -379,7 +379,7 @@ end
|
|||||||
---@return string|nil iface CC peripheral interface
|
---@return string|nil iface CC peripheral interface
|
||||||
function ppm.get_iface(device)
|
function ppm.get_iface(device)
|
||||||
if device then
|
if device then
|
||||||
for iface, data in pairs(ppm_sys.mounts) do
|
for iface, data in pairs(_ppm.mounts) do
|
||||||
if data.dev == device then return iface end
|
if data.dev == device then return iface end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -392,8 +392,8 @@ end
|
|||||||
---@param iface string CC peripheral interface
|
---@param iface string CC peripheral interface
|
||||||
---@return { [string]: function }|nil device function table
|
---@return { [string]: function }|nil device function table
|
||||||
function ppm.get_periph(iface)
|
function ppm.get_periph(iface)
|
||||||
if ppm_sys.mounts[iface] then
|
if _ppm.mounts[iface] then
|
||||||
return ppm_sys.mounts[iface].dev
|
return _ppm.mounts[iface].dev
|
||||||
else return nil end
|
else return nil end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -402,8 +402,8 @@ end
|
|||||||
---@param iface string CC peripheral interface
|
---@param iface string CC peripheral interface
|
||||||
---@return string|nil type
|
---@return string|nil type
|
||||||
function ppm.get_type(iface)
|
function ppm.get_type(iface)
|
||||||
if ppm_sys.mounts[iface] then
|
if _ppm.mounts[iface] then
|
||||||
return ppm_sys.mounts[iface].type
|
return _ppm.mounts[iface].type
|
||||||
else return nil end
|
else return nil end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -414,7 +414,7 @@ end
|
|||||||
function ppm.get_all_devices(name)
|
function ppm.get_all_devices(name)
|
||||||
local devices = {}
|
local devices = {}
|
||||||
|
|
||||||
for _, data in pairs(ppm_sys.mounts) do
|
for _, data in pairs(_ppm.mounts) do
|
||||||
if data.type == name then
|
if data.type == name then
|
||||||
table.insert(devices, data.dev)
|
table.insert(devices, data.dev)
|
||||||
end
|
end
|
||||||
@ -430,7 +430,7 @@ end
|
|||||||
function ppm.get_device(name)
|
function ppm.get_device(name)
|
||||||
local device = nil
|
local device = nil
|
||||||
|
|
||||||
for _, data in pairs(ppm_sys.mounts) do
|
for _, data in pairs(_ppm.mounts) do
|
||||||
if data.type == name then
|
if data.type == name then
|
||||||
device = data.dev
|
device = data.dev
|
||||||
break
|
break
|
||||||
@ -453,9 +453,9 @@ function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAda
|
|||||||
---@return Modem|nil modem function table
|
---@return Modem|nil modem function table
|
||||||
function ppm.get_wireless_modem()
|
function ppm.get_wireless_modem()
|
||||||
local w_modem = nil
|
local w_modem = nil
|
||||||
local emulated_env = true
|
local emulated_env = periphemu ~= nil
|
||||||
|
|
||||||
for _, device in pairs(ppm_sys.mounts) do
|
for _, device in pairs(_ppm.mounts) do
|
||||||
if device.type == "modem" and (emulated_env or device.dev.isWireless()) then
|
if device.type == "modem" and (emulated_env or device.dev.isWireless()) then
|
||||||
w_modem = device.dev
|
w_modem = device.dev
|
||||||
break
|
break
|
||||||
@ -471,7 +471,7 @@ end
|
|||||||
function ppm.get_monitor_list()
|
function ppm.get_monitor_list()
|
||||||
local list = {}
|
local list = {}
|
||||||
|
|
||||||
for iface, device in pairs(ppm_sys.mounts) do
|
for iface, device in pairs(_ppm.mounts) do
|
||||||
if device.type == "monitor" then list[iface] = device end
|
if device.type == "monitor" then list[iface] = device end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ local t_pack = table.pack
|
|||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
-- scada-common version
|
-- scada-common version
|
||||||
util.version = "1.5.4"
|
util.version = "1.5.5"
|
||||||
|
|
||||||
util.TICK_TIME_S = 0.05
|
util.TICK_TIME_S = 0.05
|
||||||
util.TICK_TIME_MS = 50
|
util.TICK_TIME_MS = 50
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user