Compare commits

..

17 Commits
devel ... main

Author SHA1 Message Date
9d4cd13911 changed to raw
Some checks failed
Lua Checks / check (push) Successful in 6s
Deploy Installation Data / deploy (push) Failing after 10s
2025-10-29 13:53:09 +01:00
cc2cb8feab changed to http
Some checks failed
Lua Checks / check (push) Successful in 6s
Deploy Installation Data / deploy (push) Failing after 11s
2025-10-29 13:51:54 +01:00
c0dcffe14a Changed installer to use Befator inc git
Some checks failed
Lua Checks / check (push) Successful in 5s
Deploy Installation Data / deploy (push) Failing after 10s
2025-10-29 13:48:18 +01:00
4b8aa3e858 allowed for non-wireless modem setup
Some checks failed
Lua Checks / check (push) Successful in 9s
Deploy Installation Data / deploy (push) Failing after 1m25s
Hardcoded that the Application thinks its in an emulated env to thus allow the use of Wired modems instead of Wireless (because who wants to use wireless modems??!)
2025-10-29 13:45:08 +01:00
Mikayla
c6d526163f
Create CONTRIBUTING.md 2025-10-06 11:50:05 -04:00
Mikayla
6e7c843258
Merge pull request #631 from MikaylaFischler/devel
2025.09.14 Release
2025-09-13 16:10:00 -04:00
Mikayla
6eb9ac5845
Merge pull request #626 from MikaylaFischler/devel
2025.06.29 Release
2025-06-29 16:48:45 -04:00
Mikayla
919ca6f0af
Merge pull request #620 from MikaylaFischler/devel
2025.05.10 Release
2025-05-10 17:45:19 -04:00
Mikayla
b1ad2084f2
Merge pull request #610 from MikaylaFischler/devel
2025.02.26 Release
2025-02-26 18:52:56 -05:00
Mikayla
cf9e26ac8f
Merge pull request #599 from MikaylaFischler/devel
Pocket Beta Release
2025-01-27 12:52:32 -05:00
Mikayla
451232ce91
Merge pull request #586 from MikaylaFischler/devel
2024.12.21 Release
2024-12-21 12:30:33 -05:00
Mikayla
c6343e5956
Merge pull request #579 from MikaylaFischler/devel
2024.11.21 Release
2024-11-21 18:40:52 -05:00
Mikayla
7130176781
Merge pull request #563 from MikaylaFischler/devel
2024.10.18 Release
2024-10-18 20:34:14 -04:00
Mikayla
8e19418701
Merge pull request #547 from MikaylaFischler/devel
2024.09.08 Release
2024-09-11 21:29:36 -04:00
Mikayla
07406ca5fc
Merge pull request #542 from MikaylaFischler/devel
2024.08.25 Release
2024-08-25 22:50:18 -04:00
Mikayla
b0342654e7
added off-line installation to installation options 2024-08-12 09:55:19 -04:00
Mikayla
f725eb0eef
Merge pull request #533 from MikaylaFischler/devel
2024.07.28 Release
2024-07-28 17:21:26 -04:00
13 changed files with 539 additions and 477 deletions

57
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,57 @@
# 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.

View File

@ -45,6 +45,7 @@ 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:
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/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

View File

@ -19,7 +19,7 @@ local CCMSI_VERSION = "v1.21"
local install_dir = "/.install-cache"
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/"
local repo_path = "http://git.befatorinc.de/TheHomecraft/cc-mek-scada/raw/"
---@diagnostic disable-next-line: undefined-global
local _is_pkt_env = pocket -- luacheck: ignore pocket

View File

@ -53,6 +53,7 @@ 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("has_modem", not plc_state.no_modem)
databus.ps.publish("degraded", plc_state.degraded)
databus.ps.publish("init_ok", plc_state.init_ok)
end
-- transmit thread (routine) statuses

View File

@ -51,11 +51,11 @@ local function init(panel)
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
local degraded = LED{parent=system,label="STATUS",colors=cpair(colors.red,colors.green)}
local init_ok = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
system.line_break()
degraded.register(databus.ps, "degraded", degraded.update)
init_ok.register(databus.ps, "init_ok", init_ok.update)
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}

View File

@ -118,7 +118,7 @@ function plc.rps_init(reactor, is_formed)
reactor_enabled = false,
enabled_at = 0,
emer_cool_active = nil, ---@type boolean
formed = is_formed, ---@type boolean|nil
formed = is_formed,
force_disabled = false,
tripped = false,
trip_cause = "ok" ---@type rps_trip_cause
@ -364,35 +364,29 @@ function plc.rps_init(reactor, is_formed)
return public.activate()
end
-- check all safety conditions if we have a formed reactor, otherwise handle a subset of conditions
-- check all safety conditions
---@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
function public.check(has_reactor)
function public.check()
local status = RPS_TRIP_CAUSE.OK
local was_tripped = self.tripped
local first_trip = false
if has_reactor then
if self.formed then
-- update state
parallel.waitForAll(
_is_formed,
_is_force_disabled,
_high_damage,
_high_temp,
_low_coolant,
_excess_waste,
_excess_heated_coolant,
_insufficient_fuel
)
else
-- check to see if its now formed
_is_formed()
end
if self.formed then
-- update state
parallel.waitForAll(
_is_formed,
_is_force_disabled,
_high_damage,
_high_temp,
_low_coolant,
_excess_waste,
_excess_heated_coolant,
_insufficient_fuel
)
else
self.formed = nil
self.state[CHK.SYS_FAIL] = true
-- check to see if its now formed
_is_formed()
end
-- check system states in order of severity
@ -480,7 +474,6 @@ function plc.rps_init(reactor, is_formed)
---@nodiscard
function public.is_active() return self.reactor_enabled end
---@nodiscard
---@return boolean|nil formed true if formed, false if not, nil if unknown
function public.is_formed() return self.formed end
---@nodiscard
function public.is_force_disabled() return self.force_disabled end
@ -502,14 +495,14 @@ function plc.rps_init(reactor, is_formed)
end
-- partial RPS reset that only clears fault and sys_fail
function public.reset_reattach()
function public.reset_formed()
self.tripped = false
self.trip_cause = RPS_TRIP_CAUSE.OK
self.state[CHK.FAULT] = false
self.state[CHK.SYS_FAIL] = false
log.info("RPS: partial reset on connected or formed")
log.info("RPS: partial reset on formed")
end
-- reset the automatic and timeout trip flags, then clear trip if that was the trip cause
@ -591,7 +584,11 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
-- dynamic reactor status information, excluding heating rate
---@return table data_table, boolean faulted
local function _get_reactor_status()
local fuel, waste, coolant, hcoolant = nil, nil, nil, nil
local fuel = nil
local waste = nil
local coolant = nil
local hcoolant = nil
local data_table = {}
reactor.__p_disable_afc()
@ -710,112 +707,6 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
reactor.__p_enable_afc()
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 --
---@class plc_comms
@ -857,8 +748,8 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
---@param formed boolean reactor formed (from PLC state)
function public.send_status(no_reactor, formed)
if self.linked then
local mek_data = nil ---@type table
local heating_rate = 0.0 ---@type number
local mek_data = nil ---@type table
local heating_rate = 0.0 ---@type number
if (not no_reactor) and rps.is_formed() then
if _update_status_cache() then mek_data = self.status_cache end
@ -912,11 +803,15 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
-- get as RPLC packet
if s_pkt.protocol() == PROTOCOL.RPLC then
local rplc_pkt = comms.rplc_packet()
if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end
if rplc_pkt.decode(s_pkt) then
pkt = rplc_pkt.get()
end
-- get as SCADA management packet
elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
local mgmt_pkt = comms.mgmt_packet()
if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() end
if mgmt_pkt.decode(s_pkt) then
pkt = mgmt_pkt.get()
end
else
log.debug("unsupported packet type " .. s_pkt.protocol(), true)
end
@ -928,13 +823,16 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
-- handle RPLC and MGMT packets
---@param packet rplc_frame|mgmt_frame packet frame
---@param plc_state plc_state PLC state
---@param setpoints plc_setpoints setpoint control table
---@param println_ts function console print, when UI isn't running
function public.handle_packet(packet, plc_state, setpoints, println_ts)
---@param setpoints setpoints setpoint control table
function public.handle_packet(packet, plc_state, setpoints)
-- print a log message to the terminal as long as the UI isn't running
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 l_chan = packet.scada_frame.local_channel()
local src_addr = packet.scada_frame.src_addr()
-- handle packets now that we have prints setup
if l_chan == config.PLC_Channel then
-- check sequence number
if self.r_seq_num == nil then
@ -969,7 +867,36 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
log.debug("sent out structure again, did supervisor miss it?")
elseif packet.type == RPLC_TYPE.MEK_BURN_RATE then
-- set the burn rate
_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
elseif packet.type == RPLC_TYPE.RPS_ENABLE then
-- enable the reactor
self.scrammed = false
@ -998,7 +925,68 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog)
_send_ack(packet.type, true)
elseif packet.type == RPLC_TYPE.AUTO_BURN_RATE then
-- automatic control requested a new burn rate
_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
else
log.debug("received unknown RPLC packet type " .. packet.type)
end

View File

@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
local renderer = require("reactor-plc.renderer")
local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "v1.9.0"
local R_PLC_VERSION = "v1.8.22"
local println = util.println
local println_ts = util.println_ts
@ -87,6 +87,7 @@ local function main()
-- PLC system state flags
---@class plc_state
plc_state = {
init_ok = true,
fp_ok = false,
shutdown = false,
degraded = true,
@ -96,22 +97,19 @@ local function main()
},
-- control setpoints
---@class plc_setpoints
---@class setpoints
setpoints = {
burn_rate_en = false,
burn_rate = 0.0
},
-- core PLC devices
---@class plc_dev
plc_dev = {
---@diagnostic disable-next-line: assign-type-mismatch
reactor = ppm.get_fission_reactor(), ---@type table
reactor = ppm.get_fission_reactor(),
modem = ppm.get_wireless_modem()
},
-- system objects
---@class plc_sys
plc_sys = {
rps = nil, ---@type rps
nic = nil, ---@type nic
@ -138,20 +136,14 @@ local function main()
-- we need a reactor, can at least do some things even if it isn't formed though
if plc_state.no_reactor then
println("startup> fission reactor not found")
log.warning("startup> no reactor on startup")
println("init> fission reactor not found")
log.warning("init> no reactor on startup")
plc_state.init_ok = false
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
println("startup> fission reactor is not formed")
log.warning("startup> reactor logic adapter present, but reactor is not formed")
println("init> fission reactor is not formed")
log.warning("init> reactor logic adapter present, but reactor is not formed")
plc_state.degraded = true
plc_state.reactor_formed = false
@ -159,74 +151,89 @@ local function main()
-- modem is required if networked
if __shared_memory.networked and plc_state.no_modem then
println("startup> wireless modem not found")
log.warning("startup> no wireless modem on startup")
println("init> wireless modem not found")
log.warning("init> no wireless modem on startup")
-- scram reactor if present and enabled
if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
smem_dev.reactor.scram()
end
plc_state.init_ok = false
plc_state.degraded = true
end
-- 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
log.debug("startup> power-on SCRAM")
smem_dev.reactor.scram()
end
-- setup front panel
local message
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
-- ...or not
if not plc_state.fp_ok then
println_ts(util.c("UI error: ", message))
println("startup> running without front panel")
log.error(util.c("front panel GUI render failed with error ", message))
log.info("startup> running in headless mode without front panel")
end
-- 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
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
if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then
smem_dev.reactor.scram()
end
-- setup front panel
if not renderer.ui_ready() then
local message
plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode)
-- ...or not
if not plc_state.fp_ok then
println_ts(util.c("UI error: ", message))
println("init> running without front panel")
log.error(util.c("front panel GUI render failed with error ", message))
log.info("init> running in headless mode without front panel")
end
end
if plc_state.init_ok then
-- init reactor protection system
smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed)
log.debug("init> rps init")
if __shared_memory.networked then
-- comms watchdog
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
log.debug("init> conn watchdog started")
-- create network interface then setup comms
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)
log.debug("init> comms init")
else
_println_no_fp("init> starting in offline mode")
log.info("init> running 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
databus.tx_hw_status(plc_state)
end
----------------------------------------
-- start system
----------------------------------------
-- initialize PLC
----------------------------------------
-- init reactor protection system
smem_sys.rps = plc.rps_init(smem_dev.reactor, util.trinary(plc_state.no_reactor, nil, plc_state.reactor_formed))
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
-- comms watchdog
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
log.debug("startup> conn watchdog started")
-- create network interface then setup comms
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)
log.debug("startup> comms init")
else
_println_no_fp("startup> starting in non-networked mode")
log.info("startup> starting without networking")
end
databus.tx_hw_status(plc_state)
_println_no_fp("startup> completed")
log.info("startup> completed")
init()
-- init threads
local main_thread = threads.thread__main(__shared_memory)
local main_thread = threads.thread__main(__shared_memory, init)
local rps_thread = threads.thread__rps(__shared_memory)
if __shared_memory.networked then
@ -240,12 +247,14 @@ local function main()
-- 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)
-- 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_rps_status()
if plc_state.init_ok then
-- 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_rps_status()
-- close connection
smem_sys.plc_comms.close()
-- close connection
smem_sys.plc_comms.close()
end
else
-- run threads, excluding comms
parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec)

View File

@ -31,7 +31,8 @@ local MQ__COMM_CMD = {
-- main thread
---@nodiscard
---@param smem plc_shared_memory
function threads.thread__main(smem)
---@param init function
function threads.thread__main(smem, init)
-- 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
@ -41,7 +42,7 @@ function threads.thread__main(smem)
-- execute thread
function public.exec()
databus.tx_rt_status("main", true)
log.debug("OS: main thread start")
log.debug("main thread init, clock inactive")
-- 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)
@ -54,9 +55,6 @@ function threads.thread__main(smem)
local plc_state = smem.plc_state
local plc_dev = smem.plc_dev
-- start clock
loop_clock.start()
-- event loop
while true do
-- get plc_sys fields (may have been set late due to degraded boot)
@ -69,6 +67,7 @@ function threads.thread__main(smem)
-- handle event
if event == "timer" and loop_clock.is_clock(param1) then
-- note: loop clock is only running if init_ok = true
-- blink heartbeat indicator
databus.heartbeat()
@ -94,7 +93,7 @@ function threads.thread__main(smem)
-- reactor now formed
plc_state.reactor_formed = true
println_ts("reactor is now formed")
println_ts("reactor is now formed.")
log.info("reactor is now formed")
-- SCRAM newly formed reactor
@ -107,10 +106,10 @@ function threads.thread__main(smem)
-- partial reset of RPS, specific to becoming formed
-- without this, auto control can't resume on chunk load
rps.reset_reattach()
elseif plc_state.reactor_formed and (rps.is_formed() == false) then
rps.reset_formed()
elseif plc_state.reactor_formed and not rps.is_formed() then
-- 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")
plc_state.reactor_formed = false
@ -119,14 +118,14 @@ function threads.thread__main(smem)
-- update indicators
databus.tx_hw_status(plc_state)
elseif event == "modem_message" and networked and nic.is_connected() then
elseif event == "modem_message" and networked and plc_state.init_ok and nic.is_connected() then
-- got a packet
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
if packet ~= nil then
-- pass the packet onto the comms message queue
smem.q.mq_comms_rx.push_packet(packet)
end
elseif event == "timer" and networked and conn_watchdog.is_timer(param1) then
elseif event == "timer" and networked and plc_state.init_ok and conn_watchdog.is_timer(param1) then
-- haven't heard from server recently? close connection and shutdown reactor
plc_comms.close()
smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT)
@ -147,7 +146,8 @@ function threads.thread__main(smem)
elseif networked and type == "modem" then
---@cast device Modem
-- we only care if this is our wireless modem
if nic.is_modem(device) then
-- note, check init_ok first since nic will be nil if it is false
if plc_state.init_ok and nic.is_modem(device) then
nic.disconnect()
println_ts("comms modem disconnected!")
@ -161,8 +161,10 @@ function threads.thread__main(smem)
plc_state.no_modem = true
plc_state.degraded = true
-- try to scram reactor if it is still connected
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
if plc_state.init_ok then
-- try to scram reactor if it is still connected
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
end
end
else
log.warning("a modem was disconnected")
@ -182,7 +184,7 @@ function threads.thread__main(smem)
plc_dev.reactor = device
plc_state.no_reactor = false
println_ts("reactor reconnected")
println_ts("reactor reconnected.")
log.info("reactor reconnected")
-- we need to assume formed here as we cannot check in this main loop
@ -194,52 +196,64 @@ function threads.thread__main(smem)
plc_state.degraded = false
end
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
if plc_state.init_ok then
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
rps.reconnect_reactor(plc_dev.reactor)
if networked then
plc_comms.reconnect_reactor(plc_dev.reactor)
rps.reconnect_reactor(plc_dev.reactor)
if networked then
plc_comms.reconnect_reactor(plc_dev.reactor)
end
-- partial reset of RPS, specific to becoming formed/reconnected
-- without this, auto control can't resume on chunk load
rps.reset_formed()
end
-- partial reset of RPS, specific to becoming formed/reconnected
-- without this, auto control can't resume on chunk load
rps.reset_reattach()
elseif networked and type == "modem" then
---@cast device Modem
-- note, check init_ok first since nic will be nil if it is false
if device.isWireless() and not nic.is_connected() then
if device.isWireless() and not (plc_state.init_ok and nic.is_connected()) then
-- reconnected modem
plc_dev.modem = device
plc_state.no_modem = false
nic.connect(device)
if plc_state.init_ok then nic.connect(device) end
println_ts("comms modem reconnected")
println_ts("wireless modem reconnected.")
log.info("comms modem reconnected")
-- determine if we are still in a degraded state
if plc_state.reactor_formed and not plc_state.no_reactor then
if not plc_state.no_reactor then
plc_state.degraded = false
end
elseif device.isWireless() then
log.info("unused wireless modem connected")
log.info("unused wireless modem reconnected")
else
log.info("wired modem connected")
log.info("wired modem reconnected")
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
databus.tx_hw_status(plc_state)
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or
event == "double_click" then
-- handle a mouse event
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
-- check for termination request
if event == "terminate" or ppm.should_terminate() then
log.info("OS: terminate requested, main thread exiting")
log.info("terminate requested, main thread exiting")
-- rps handles reactor shutdown
plc_state.shutdown = true
break
@ -263,7 +277,8 @@ function threads.thread__main(smem)
-- if not, we need to restart the clock
-- this thread cannot be slept because it will miss events (namely "terminate" otherwise)
if not plc_state.shutdown then
log.info("OS: main thread restarting now...")
log.info("main thread restarting now...")
util.push_event("clock_start")
end
end
end
@ -284,7 +299,7 @@ function threads.thread__rps(smem)
-- execute thread
function public.exec()
databus.tx_rt_status("rps", true)
log.debug("OS: rps thread start")
log.debug("rps thread start")
-- load in from shared memory
local networked = smem.networked
@ -301,36 +316,49 @@ function threads.thread__rps(smem)
-- get plc_sys fields (may have been set late due to degraded boot)
local rps = smem.plc_sys.rps
local plc_comms = smem.plc_sys.plc_comms
-- get reactor, it may have changed due to a disconnect/reconnect
-- get reactor, may have changed do to disconnect/reconnect
local reactor = plc_dev.reactor
-- SCRAM if no open connection
if networked and not plc_comms.is_linked() then
if was_linked then
was_linked = false
rps.trip_timeout()
-- RPS checks
if plc_state.init_ok then
-- SCRAM if no open connection
if networked and not plc_comms.is_linked() then
if was_linked then
was_linked = false
rps.trip_timeout()
end
else
was_linked = true
end
else was_linked = true end
-- check reactor status
if (not plc_state.no_reactor) and rps.is_formed() then
local reactor_status = reactor.getStatus()
databus.tx_reactor_state(reactor_status)
if (not plc_state.no_reactor) and rps.is_formed() then
-- check reactor status
---@diagnostic disable-next-line: need-check-nil
local reactor_status = reactor.getStatus()
databus.tx_reactor_state(reactor_status)
-- 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)
if rps.is_tripped() and reactor_status then rps.scram() end
end
-- 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)
if rps.is_tripped() and reactor_status then
rps.scram()
end
end
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
-- if we are in standalone mode and the front panel isn't working, continuously reset RPS
-- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable
if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end
-- check safety (SCRAM occurs if tripped)
local rps_tripped, rps_status_string, rps_first = rps.check(not plc_state.no_reactor)
if rps_tripped and rps_first then
println_ts("RPS: SCRAM on safety trip (" .. rps_status_string .. ")")
if networked then plc_comms.send_rps_alarm(rps_status_string) end
-- check safety (SCRAM occurs if tripped)
if not plc_state.no_reactor then
local rps_tripped, rps_status_string, rps_first = rps.check()
if rps_tripped and rps_first then
println_ts("[RPS] SCRAM! safety trip: " .. rps_status_string)
if networked and not plc_state.no_modem then
plc_comms.send_rps_alarm(rps_status_string)
end
end
end
end
-- check for messages in the message queue
@ -340,19 +368,19 @@ function threads.thread__rps(smem)
if msg ~= nil then
if msg.qtype == mqueue.TYPE.COMMAND then
-- received a command
if msg.message == MQ__RPS_CMD.SCRAM then
-- SCRAM
log.info("RPS: OS requested SCRAM")
rps.scram()
elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then
-- lost peripheral(s)
log.info("RPS: received PLC degraded alert")
rps.trip_fault()
elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then
-- watchdog tripped
println_ts("RPS: supervisor timeout")
log.warning("RPS: received supervisor timeout alert")
rps.trip_timeout()
if plc_state.init_ok then
if msg.message == MQ__RPS_CMD.SCRAM then
-- SCRAM
rps.scram()
elseif msg.message == MQ__RPS_CMD.DEGRADED_SCRAM then
-- lost peripheral(s)
rps.trip_fault()
elseif msg.message == MQ__RPS_CMD.TRIP_TIMEOUT then
-- watchdog tripped
rps.trip_timeout()
println_ts("server timeout")
log.warning("server timeout")
end
end
elseif msg.qtype == mqueue.TYPE.DATA then
-- received data
@ -368,17 +396,17 @@ function threads.thread__rps(smem)
-- check for termination request
if plc_state.shutdown then
-- safe exit
log.info("OS: rps thread shutdown initiated")
if rps.scram() then
println_ts("exiting, reactor disabled")
log.info("OS: rps thread reactor SCRAM OK on exit")
else
println_ts("exiting, reactor failed to disable")
log.error("OS: rps thread failed to SCRAM reactor on exit")
log.info("rps thread shutdown initiated")
if plc_state.init_ok then
if rps.scram() then
println_ts("reactor disabled")
log.info("rps thread reactor SCRAM OK")
else
println_ts("exiting, reactor failed to disable")
log.error("rps thread failed to SCRAM reactor on exit")
end
end
log.info("OS: rps thread exiting")
log.info("rps thread exiting")
break
end
@ -400,8 +428,8 @@ function threads.thread__rps(smem)
databus.tx_rt_status("rps", false)
if not plc_state.shutdown then
smem.plc_sys.rps.scram()
log.info("OS: rps thread restarting in 5 seconds...")
if plc_state.init_ok then smem.plc_sys.rps.scram() end
log.info("rps thread restarting in 5 seconds...")
util.psleep(5)
end
end
@ -420,7 +448,7 @@ function threads.thread__comms_tx(smem)
-- execute thread
function public.exec()
databus.tx_rt_status("comms_tx", true)
log.debug("OS: comms tx thread start")
log.debug("comms tx thread start")
-- load in from shared memory
local plc_state = smem.plc_state
@ -437,7 +465,7 @@ function threads.thread__comms_tx(smem)
while comms_queue.ready() and not plc_state.shutdown do
local msg = comms_queue.pop()
if msg ~= nil then
if msg ~= nil and plc_state.init_ok then
if msg.qtype == mqueue.TYPE.COMMAND then
-- received a command
if msg.message == MQ__COMM_CMD.SEND_STATUS then
@ -458,7 +486,7 @@ function threads.thread__comms_tx(smem)
-- check for termination request
if plc_state.shutdown then
log.info("OS: comms tx thread exiting")
log.info("comms tx thread exiting")
break
end
@ -480,7 +508,7 @@ function threads.thread__comms_tx(smem)
databus.tx_rt_status("comms_tx", false)
if not plc_state.shutdown then
log.info("OS: comms tx thread restarting in 5 seconds...")
log.info("comms tx thread restarting in 5 seconds...")
util.psleep(5)
end
end
@ -493,16 +521,13 @@ end
---@nodiscard
---@param smem plc_shared_memory
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
local public = {}
-- execute thread
function public.exec()
databus.tx_rt_status("comms_rx", true)
log.debug("OS: comms rx thread start")
log.debug("comms rx thread start")
-- load in from shared memory
local plc_state = smem.plc_state
@ -521,7 +546,7 @@ function threads.thread__comms_rx(smem)
while comms_queue.ready() and not plc_state.shutdown do
local msg = comms_queue.pop()
if msg ~= nil then
if msg ~= nil and plc_state.init_ok then
if msg.qtype == mqueue.TYPE.COMMAND then
-- received a command
elseif msg.qtype == mqueue.TYPE.DATA then
@ -530,7 +555,7 @@ function threads.thread__comms_rx(smem)
-- received a packet
-- handle the packet (setpoints passed to update burn rate setpoint)
-- (plc_state passed to check if degraded)
plc_comms.handle_packet(msg.message, plc_state, setpoints, println_ts)
plc_comms.handle_packet(msg.message, plc_state, setpoints)
end
end
@ -540,7 +565,7 @@ function threads.thread__comms_rx(smem)
-- check for termination request
if plc_state.shutdown then
log.info("OS: comms rx thread exiting")
log.info("comms rx thread exiting")
break
end
@ -562,7 +587,7 @@ function threads.thread__comms_rx(smem)
databus.tx_rt_status("comms_rx", false)
if not plc_state.shutdown then
log.info("OS: comms rx thread restarting in 5 seconds...")
log.info("comms rx thread restarting in 5 seconds...")
util.psleep(5)
end
end
@ -581,7 +606,7 @@ function threads.thread__setpoint_control(smem)
-- execute thread
function public.exec()
databus.tx_rt_status("spctl", true)
log.debug("OS: setpoint control thread start")
log.debug("setpoint control thread start")
-- load in from shared memory
local plc_state = smem.plc_state
@ -604,7 +629,9 @@ function threads.thread__setpoint_control(smem)
-- get reactor, may have changed do to disconnect/reconnect
local reactor = plc_dev.reactor
if not plc_state.no_reactor then
if plc_state.init_ok and (not plc_state.no_reactor) then
---@cast reactor table won't be nil
-- check if we should start ramping
if setpoints.burn_rate_en and (setpoints.burn_rate ~= last_burn_sp) then
local cur_burn_rate = reactor.getBurnRate()
@ -671,7 +698,7 @@ function threads.thread__setpoint_control(smem)
-- check for termination request
if plc_state.shutdown then
log.info("OS: setpoint control thread exiting")
log.info("setpoint control thread exiting")
break
end
@ -693,7 +720,7 @@ function threads.thread__setpoint_control(smem)
databus.tx_rt_status("spctl", false)
if not plc_state.shutdown then
log.info("OS: setpoint control thread restarting in 5 seconds...")
log.info("setpoint control thread restarting in 5 seconds...")
util.psleep(5)
end
end

View File

@ -205,7 +205,7 @@ function comms.scada_packet()
if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then
-- outside of maximum allowable transmission distance
-- log.debug("COMMS: comms.scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
-- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
else
if type(self.raw) == "table" 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
-- outside of maximum allowable transmission distance
-- log.debug("COMMS: comms.authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
-- log.debug("comms.authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
else
if type(self.raw) == "table" 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 }
for i = 1, self.length do insert(self.raw, data[i]) end
else
log.error("COMMS: modbus_packet.make(): data not a table")
log.error("comms.modbus_packet.make(): data not a table")
end
end
@ -435,11 +435,11 @@ function comms.modbus_packet()
return size_ok and valid
else
log.debug("COMMS: attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log.debug("COMMS: nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@ -498,7 +498,7 @@ function comms.rplc_packet()
self.raw = { self.id, self.type }
for i = 1, #data do insert(self.raw, data[i]) end
else
log.error("COMMS: rplc_packet.make(): data not a table")
log.error("comms.rplc_packet.make(): data not a table")
end
end
@ -521,11 +521,11 @@ function comms.rplc_packet()
return ok
else
log.debug("COMMS: attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log.debug("COMMS: nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@ -580,7 +580,7 @@ function comms.mgmt_packet()
self.raw = { self.type }
for i = 1, #data do insert(self.raw, data[i]) end
else
log.error("COMMS: mgmt_packet.make(): data not a table")
log.error("comms.mgmt_packet.make(): data not a table")
end
end
@ -601,11 +601,11 @@ function comms.mgmt_packet()
return ok
else
log.debug("COMMS: attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log.debug("COMMS: nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end
@ -659,7 +659,7 @@ function comms.crdn_packet()
self.raw = { self.type }
for i = 1, #data do insert(self.raw, data[i]) end
else
log.error("COMMS: crdn_packet.make(): data not a table")
log.error("comms.crdn_packet.make(): data not a table")
end
end
@ -680,11 +680,11 @@ function comms.crdn_packet()
return ok
else
log.debug("COMMS: attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
log.debug("attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log.debug("COMMS: nil frame encountered", true)
log.debug("nil frame encountered", true)
return false
end
end

View File

@ -20,7 +20,7 @@ local MODE = { APPEND = 0, NEW = 1 }
log.MODE = MODE
local _log = {
local logger = {
not_ready = true,
path = "/log.txt",
mode = MODE.APPEND,
@ -42,36 +42,36 @@ local free_space = fs.getFreeSpace
---@param err_msg string|nil error message
---@return boolean out_of_space
local function check_out_of_space(err_msg)
return (free_space(_log.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
return (free_space(logger.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
end
-- private log write function
---@param msg_bits any[]
local function write_log(msg_bits)
if _log.not_ready then return end
local function _log(msg_bits)
if logger.not_ready then return end
local time_stamp = os.date(TIME_FMT)
local stamped = util.c(time_stamp, table.unpack(msg_bits))
-- attempt to write log
local status, result = pcall(function ()
_log.file.writeLine(stamped)
_log.file.flush()
logger.file.writeLine(stamped)
logger.file.flush()
end)
-- if we don't have space, we need to create a new log file
if check_out_of_space() then
-- delete the old log file before opening a new one
_log.file.close()
fs.delete(_log.path)
logger.file.close()
fs.delete(logger.path)
-- re-init logger and pass dmesg_out so that it doesn't change
log.init(_log.path, _log.mode, _log.debug, _log.dmesg_out)
log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out)
-- log the message and recycle warning
_log.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
_log.file.writeLine(stamped)
_log.file.flush()
logger.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
logger.file.writeLine(stamped)
logger.file.flush()
elseif (not status) and (result ~= nil) then
util.println("unexpected error writing to the log file: " .. result)
end
@ -89,45 +89,45 @@ end
function log.init(path, write_mode, include_debug, dmesg_redirect)
local err_msg
_log.path = path
_log.mode = write_mode
_log.debug = include_debug
_log.file, err_msg = fs.open(path, util.trinary(_log.mode == MODE.APPEND, "a", "w"))
logger.path = path
logger.mode = write_mode
logger.debug = include_debug
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
if dmesg_redirect then
_log.dmesg_out = dmesg_redirect
logger.dmesg_out = dmesg_redirect
else
_log.dmesg_out = term.current()
logger.dmesg_out = term.current()
end
-- check for space issues
local out_of_space = check_out_of_space(err_msg)
-- try to handle problems
if _log.file == nil or out_of_space then
if logger.file == nil or out_of_space then
if out_of_space then
if fs.exists(_log.path) then
fs.delete(_log.path)
if fs.exists(logger.path) then
fs.delete(logger.path)
_log.file, err_msg = fs.open(path, util.trinary(_log.mode == MODE.APPEND, "a", "w"))
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
if _log.file then
_log.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
_log.file.flush()
if logger.file then
logger.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
logger.file.flush()
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("unexpected error setting up the log file: " .. err_msg) end
end
_log.not_ready = false
logger.not_ready = false
end
-- close the log file handle
function log.close() _log.file.close() end
function log.close() logger.file.close() end
-- direct dmesg output to a monitor/window
---@param window Window window or terminal reference
function log.direct_dmesg(window) _log.dmesg_out = window end
function log.direct_dmesg(window) logger.dmesg_out = window end
-- dmesg style logging for boot because I like linux-y things
---@param msg any message
@ -142,7 +142,7 @@ function log.dmesg(msg, tag, tag_color)
tag = util.strval(tag or "")
local t_stamp = string.format("%12.2f", os.clock())
local out = _log.dmesg_out
local out = logger.dmesg_out
if out ~= nil then
local out_w, out_h = out.getSize()
@ -180,7 +180,7 @@ function log.dmesg(msg, tag, tag_color)
if cur_y == out_h then
out.scroll(1)
out.setCursorPos(1, cur_y)
_log.dmesg_scroll_count = _log.dmesg_scroll_count + 1
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
else
out.setCursorPos(1, cur_y + 1)
end
@ -216,7 +216,7 @@ function log.dmesg(msg, tag, tag_color)
if cur_y == out_h then
out.scroll(1)
out.setCursorPos(1, cur_y)
_log.dmesg_scroll_count = _log.dmesg_scroll_count + 1
logger.dmesg_scroll_count = logger.dmesg_scroll_count + 1
else
out.setCursorPos(1, cur_y + 1)
end
@ -225,9 +225,9 @@ function log.dmesg(msg, tag, tag_color)
out.write(lines[i])
end
_log.dmesg_restore_coord = { out.getCursorPos() }
logger.dmesg_restore_coord = { out.getCursorPos() }
write_log{"[", t_stamp, "] [", tag, "] ", msg}
_log{"[", t_stamp, "] [", tag, "] ", msg}
end
return ts_coord
@ -241,9 +241,9 @@ end
---@return function update, function done
function log.dmesg_working(msg, tag, tag_color)
local ts_coord = log.dmesg(msg, tag, tag_color)
local initial_scroll = _log.dmesg_scroll_count
local initial_scroll = logger.dmesg_scroll_count
local out = _log.dmesg_out
local out = logger.dmesg_out
local width = (ts_coord.x2 - ts_coord.x1) + 1
if out ~= nil then
@ -252,7 +252,7 @@ function log.dmesg_working(msg, tag, tag_color)
local counter = 0
local function update(sec_remaining)
local new_y = ts_coord.y - (_log.dmesg_scroll_count - initial_scroll)
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
if new_y < 1 then return end
local time = util.sprintf("%ds", sec_remaining)
@ -280,11 +280,11 @@ function log.dmesg_working(msg, tag, tag_color)
counter = counter + 1
out.setCursorPos(table.unpack(_log.dmesg_restore_coord))
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
end
local function done(ok)
local new_y = ts_coord.y - (_log.dmesg_scroll_count - initial_scroll)
local new_y = ts_coord.y - (logger.dmesg_scroll_count - initial_scroll)
if new_y < 1 then return end
out.setCursorPos(ts_coord.x1, new_y)
@ -299,7 +299,7 @@ function log.dmesg_working(msg, tag, tag_color)
out.setTextColor(initial_color)
out.setCursorPos(table.unpack(_log.dmesg_restore_coord))
out.setCursorPos(table.unpack(logger.dmesg_restore_coord))
end
return update, done
@ -312,28 +312,28 @@ end
---@param msg any message
---@param trace? boolean include file trace
function log.debug(msg, trace)
if _log.debug then
if logger.debug then
if trace then
local info = debug.getinfo(2)
if info.name ~= nil then
write_log{DBG_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
_log{DBG_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
else
write_log{DBG_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
_log{DBG_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
end
else
write_log{DBG_TAG, msg}
_log{DBG_TAG, msg}
end
end
end
-- log info messages
---@param msg any message
function log.info(msg) write_log{INF_TAG, msg} end
function log.info(msg) _log{INF_TAG, msg} end
-- log warning messages
---@param msg any message
function log.warning(msg) write_log{WRN_TAG, msg} end
function log.warning(msg) _log{WRN_TAG, msg} end
-- log error messages
---@param msg any message
@ -343,17 +343,17 @@ function log.error(msg, trace)
local info = debug.getinfo(2)
if info.name ~= nil then
write_log{ERR_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
_log{ERR_TAG, info.short_src, COLON, info.name, FUNC, info.currentline, ARROW, msg}
else
write_log{ERR_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
_log{ERR_TAG, info.short_src, COLON, info.currentline, ARROW, msg}
end
else
write_log{ERR_TAG, msg}
_log{ERR_TAG, msg}
end
end
-- log fatal errors
---@param msg any message
function log.fatal(msg) write_log{FTL_TAG, msg} end
function log.fatal(msg) _log{FTL_TAG, msg} end
return log

View File

@ -1,10 +1,9 @@
--
-- Network Communications and Message Authentication
-- Network Communications
--
local comms = require("scada-common.comms")
local log = require("scada-common.log")
local ppm = require("scada-common.ppm")
local util = require("scada-common.util")
local md5 = require("lockbox.digest.md5")
@ -18,7 +17,7 @@ local array = require("lockbox.util.array")
local network = {}
-- cryptography engine
local _crypt = {
local c_eng = {
key = nil,
hmac = nil
}
@ -40,23 +39,23 @@ function network.init_mac(passkey)
key_deriv.setPassword(passkey)
key_deriv.finish()
_crypt.key = array.fromHex(key_deriv.asHex())
c_eng.key = array.fromHex(key_deriv.asHex())
-- initialize HMAC
_crypt.hmac = hmac()
_crypt.hmac.setBlockSize(64)
_crypt.hmac.setDigest(md5)
_crypt.hmac.setKey(_crypt.key)
c_eng.hmac = hmac()
c_eng.hmac.setBlockSize(64)
c_eng.hmac.setDigest(md5)
c_eng.hmac.setKey(c_eng.key)
local init_time = util.time_ms() - start
log.info("NET: network.init_mac completed in " .. init_time .. "ms")
log.info("network.init_mac completed in " .. init_time .. "ms")
return init_time
end
-- de-initialize message authentication system
function network.deinit_mac()
_crypt.key, _crypt.hmac = nil, nil
c_eng.key, c_eng.hmac = nil, nil
end
-- generate HMAC of message
@ -65,41 +64,29 @@ end
local function compute_hmac(message)
-- local start = util.time_ms()
_crypt.hmac.init()
_crypt.hmac.update(stream.fromString(message))
_crypt.hmac.finish()
c_eng.hmac.init()
c_eng.hmac.update(stream.fromString(message))
c_eng.hmac.finish()
local hash = _crypt.hmac.asHex()
local hash = c_eng.hmac.asHex()
-- log.debug("NET: compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)")
-- log.debug("compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)")
return hash
end
-- NIC: Network Interface Controller<br>
-- utilizes HMAC-MD5 for message authentication, if enabled and this is wireless
---@param modem Modem|nil modem to use
-- utilizes HMAC-MD5 for message authentication, if enabled
---@param modem Modem modem to use
function network.nic(modem)
local self = {
-- 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
connected = true, -- used to avoid costly MAC calculations if modem isn't even present
channels = {}
}
---@class nic:Modem
local public = {}
-- get the phy name
---@nodiscard
function public.phy_name() return self.name end
-- check if this NIC has a connected modem
---@nodiscard
function public.is_connected() return self.connected end
@ -108,14 +95,9 @@ function network.nic(modem)
---@param reconnected_modem Modem
function public.connect(reconnected_modem)
modem = reconnected_modem
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()
-- open previously opened channels
for _, channel in ipairs(self.channels) do
modem.open(channel)
end
@ -135,13 +117,13 @@ function network.nic(modem)
function public.is_modem(device) return device == modem end
-- wrap modem functions, then create custom functions
if modem then public.connect(modem) end
public.connect(modem)
-- open a channel on the modem<br>
-- if disconnected *after* opening, previousy opened channels will be re-opened on reconnection
---@param channel integer
function public.open(channel)
if modem then modem.open(channel) end
modem.open(channel)
local already_open = false
for i = 1, #self.channels do
@ -159,7 +141,7 @@ function network.nic(modem)
-- close a channel on the modem
---@param channel integer
function public.close(channel)
if modem then modem.close(channel) end
modem.close(channel)
for i = 1, #self.channels do
if self.channels[i] == channel then
@ -171,7 +153,7 @@ function network.nic(modem)
-- close all channels on the modem
function public.closeAll()
if modem then modem.closeAll() end
modem.closeAll()
self.channels = {}
end
@ -183,20 +165,17 @@ function network.nic(modem)
if self.connected then
local tx_packet = packet ---@type authd_packet|scada_packet
if self.use_hash then
if c_eng.hmac ~= nil then
-- local start = util.time_ms()
tx_packet = comms.authd_packet()
---@cast tx_packet authd_packet
tx_packet.make(packet, compute_hmac)
-- log.debug("NET: network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
-- log.debug("network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
end
---@diagnostic disable-next-line: need-check-nil
modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable())
else
log.debug("NET: network.transmit tx dropped, link is down")
end
end
@ -211,10 +190,10 @@ function network.nic(modem)
function public.receive(side, sender, reply_to, message, distance)
local packet = nil
if self.connected and side == self.iface then
if self.connected then
local s_packet = comms.scada_packet()
if self.use_hash then
if c_eng.hmac ~= nil then
-- parse packet as an authenticated SCADA packet
local a_packet = comms.authd_packet()
a_packet.receive(side, sender, reply_to, message, distance)
@ -227,10 +206,10 @@ function network.nic(modem)
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true }))
if a_packet.mac() == computed_hmac then
-- log.debug("NET: network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
-- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
s_packet.stamp_authenticated()
else
-- log.debug("NET: network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
-- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
end
end
end

View File

@ -22,7 +22,7 @@ ppm.VIRTUAL_DEVICE_TYPE = VIRTUAL_DEVICE_TYPE
local REPORT_FREQUENCY = 20 -- log every 20 faults per function
local _ppm = {
local ppm_sys = {
mounts = {}, ---@type { [string]: ppm_entry }
next_vid = 0,
auto_cf = false,
@ -66,7 +66,7 @@ local function peri_init(iface)
if status then
-- auto fault clear
if self.auto_cf then self.faulted = false end
if _ppm.auto_cf then _ppm.faulted = false end
if ppm_sys.auto_cf then ppm_sys.faulted = false end
self.fault_counts[key] = 0
@ -78,10 +78,10 @@ local function peri_init(iface)
self.faulted = true
self.last_fault = result
_ppm.faulted = true
_ppm.last_fault = result
ppm_sys.faulted = true
ppm_sys.last_fault = result
if not _ppm.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
local count_str = ""
if self.fault_counts[key] > 0 then
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
if result == "Terminated" then _ppm.terminate = true end
if result == "Terminated" then ppm_sys.terminate = true end
return ACCESS_FAULT, result
end
@ -159,10 +159,10 @@ local function peri_init(iface)
self.faulted = true
self.last_fault = UNDEFINED_FIELD
_ppm.faulted = true
_ppm.last_fault = UNDEFINED_FIELD
ppm_sys.faulted = true
ppm_sys.last_fault = UNDEFINED_FIELD
if not _ppm.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
if not ppm_sys.mute and (self.fault_counts[key] % REPORT_FREQUENCY == 0) then
local count_str = ""
if self.fault_counts[key] > 0 then
count_str = " [" .. self.fault_counts[key] .. " total calls]"
@ -193,35 +193,35 @@ end
-- REPORTING --
-- silence error prints
function ppm.disable_reporting() _ppm.mute = true end
function ppm.disable_reporting() ppm_sys.mute = true end
-- allow error prints
function ppm.enable_reporting() _ppm.mute = false end
function ppm.enable_reporting() ppm_sys.mute = false end
-- FAULT MEMORY --
-- enable automatically clearing fault flag
function ppm.enable_afc() _ppm.auto_cf = true end
function ppm.enable_afc() ppm_sys.auto_cf = true end
-- disable automatically clearing fault flag
function ppm.disable_afc() _ppm.auto_cf = false end
function ppm.disable_afc() ppm_sys.auto_cf = false end
-- clear fault flag
function ppm.clear_fault() _ppm.faulted = false end
function ppm.clear_fault() ppm_sys.faulted = false end
-- check fault flag
---@nodiscard
function ppm.is_faulted() return _ppm.faulted end
function ppm.is_faulted() return ppm_sys.faulted end
-- get the last fault message
---@nodiscard
function ppm.get_last_fault() return _ppm.last_fault end
function ppm.get_last_fault() return ppm_sys.last_fault end
-- TERMINATION --
-- if a caught error was a termination request
---@nodiscard
function ppm.should_terminate() return _ppm.terminate end
function ppm.should_terminate() return ppm_sys.terminate end
-- MOUNTING --
@ -229,12 +229,12 @@ function ppm.should_terminate() return _ppm.terminate end
function ppm.mount_all()
local ifaces = peripheral.getNames()
_ppm.mounts = {}
ppm_sys.mounts = {}
for i = 1, #ifaces do
_ppm.mounts[ifaces[i]] = peri_init(ifaces[i])
ppm_sys.mounts[ifaces[i]] = peri_init(ifaces[i])
log.info(util.c("PPM: found a ", _ppm.mounts[ifaces[i]].type, " (", ifaces[i], ")"))
log.info(util.c("PPM: found a ", ppm_sys.mounts[ifaces[i]].type, " (", ifaces[i], ")"))
end
if #ifaces == 0 then
@ -253,10 +253,10 @@ function ppm.mount(iface)
for i = 1, #ifaces do
if iface == ifaces[i] then
_ppm.mounts[iface] = peri_init(iface)
ppm_sys.mounts[iface] = peri_init(iface)
pm_type = _ppm.mounts[iface].type
pm_dev = _ppm.mounts[iface].dev
pm_type = ppm_sys.mounts[iface].type
pm_dev = ppm_sys.mounts[iface].dev
log.info(util.c("PPM: mount(", iface, ") -> found a ", pm_type))
break
@ -278,12 +278,12 @@ function ppm.remount(iface)
for i = 1, #ifaces do
if iface == ifaces[i] then
log.info(util.c("PPM: remount(", iface, ") -> is a ", pm_type))
ppm.unmount(_ppm.mounts[iface].dev)
ppm.unmount(ppm_sys.mounts[iface].dev)
_ppm.mounts[iface] = peri_init(iface)
ppm_sys.mounts[iface] = peri_init(iface)
pm_type = _ppm.mounts[iface].type
pm_dev = _ppm.mounts[iface].dev
pm_type = ppm_sys.mounts[iface].type
pm_dev = ppm_sys.mounts[iface].dev
log.info(util.c("PPM: remount(", iface, ") -> remounted a ", pm_type))
break
@ -297,24 +297,24 @@ end
---@nodiscard
---@return string type, table device
function ppm.mount_virtual()
local iface = "ppm_vdev_" .. _ppm.next_vid
local iface = "ppm_vdev_" .. ppm_sys.next_vid
_ppm.mounts[iface] = peri_init("__virtual__")
_ppm.next_vid = _ppm.next_vid + 1
ppm_sys.mounts[iface] = peri_init("__virtual__")
ppm_sys.next_vid = ppm_sys.next_vid + 1
log.info(util.c("PPM: mount_virtual() -> allocated new virtual device ", iface))
return _ppm.mounts[iface].type, _ppm.mounts[iface].dev
return ppm_sys.mounts[iface].type, ppm_sys.mounts[iface].dev
end
-- manually unmount a peripheral from the PPM
---@param device table device table
function ppm.unmount(device)
if device then
for iface, data in pairs(_ppm.mounts) do
for iface, data in pairs(ppm_sys.mounts) do
if data.dev == device then
log.warning(util.c("PPM: manually unmounted ", data.type, " mounted to ", iface))
_ppm.mounts[iface] = nil
ppm_sys.mounts[iface] = nil
break
end
end
@ -330,7 +330,7 @@ function ppm.handle_unmount(iface)
local pm_type = nil
-- what got disconnected?
local lost_dev = _ppm.mounts[iface]
local lost_dev = ppm_sys.mounts[iface]
if lost_dev then
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))
end
_ppm.mounts[iface] = nil
ppm_sys.mounts[iface] = nil
return pm_type, pm_dev
end
-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready
function ppm.log_mounts()
for iface, mount in pairs(_ppm.mounts) do
for iface, mount in pairs(ppm_sys.mounts) do
log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")"))
end
if util.table_len(_ppm.mounts) == 0 then
if util.table_len(ppm_sys.mounts) == 0 then
log.warning("PPM: no devices had been found")
end
end
@ -369,7 +369,7 @@ function ppm.list_avail() return peripheral.getNames() end
---@return { [string]: ppm_entry } mounts
function ppm.list_mounts()
local list = {}
for k, v in pairs(_ppm.mounts) do list[k] = v end
for k, v in pairs(ppm_sys.mounts) do list[k] = v end
return list
end
@ -379,7 +379,7 @@ end
---@return string|nil iface CC peripheral interface
function ppm.get_iface(device)
if device then
for iface, data in pairs(_ppm.mounts) do
for iface, data in pairs(ppm_sys.mounts) do
if data.dev == device then return iface end
end
end
@ -392,8 +392,8 @@ end
---@param iface string CC peripheral interface
---@return { [string]: function }|nil device function table
function ppm.get_periph(iface)
if _ppm.mounts[iface] then
return _ppm.mounts[iface].dev
if ppm_sys.mounts[iface] then
return ppm_sys.mounts[iface].dev
else return nil end
end
@ -402,8 +402,8 @@ end
---@param iface string CC peripheral interface
---@return string|nil type
function ppm.get_type(iface)
if _ppm.mounts[iface] then
return _ppm.mounts[iface].type
if ppm_sys.mounts[iface] then
return ppm_sys.mounts[iface].type
else return nil end
end
@ -414,7 +414,7 @@ end
function ppm.get_all_devices(name)
local devices = {}
for _, data in pairs(_ppm.mounts) do
for _, data in pairs(ppm_sys.mounts) do
if data.type == name then
table.insert(devices, data.dev)
end
@ -430,7 +430,7 @@ end
function ppm.get_device(name)
local device = nil
for _, data in pairs(_ppm.mounts) do
for _, data in pairs(ppm_sys.mounts) do
if data.type == name then
device = data.dev
break
@ -453,9 +453,9 @@ function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAda
---@return Modem|nil modem function table
function ppm.get_wireless_modem()
local w_modem = nil
local emulated_env = periphemu ~= nil
local emulated_env = true
for _, device in pairs(_ppm.mounts) do
for _, device in pairs(ppm_sys.mounts) do
if device.type == "modem" and (emulated_env or device.dev.isWireless()) then
w_modem = device.dev
break
@ -471,7 +471,7 @@ end
function ppm.get_monitor_list()
local list = {}
for iface, device in pairs(_ppm.mounts) do
for iface, device in pairs(ppm_sys.mounts) do
if device.type == "monitor" then list[iface] = device end
end

View File

@ -24,7 +24,7 @@ local t_pack = table.pack
local util = {}
-- scada-common version
util.version = "1.5.5"
util.version = "1.5.4"
util.TICK_TIME_S = 0.05
util.TICK_TIME_MS = 50