mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
Merge pull request #368 from MikaylaFischler/145-graphical-configure-utilities
RTU configurator and other updates
This commit is contained in:
commit
69680a53a0
15
ccmsi.lua
15
ccmsi.lua
@ -18,7 +18,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
local function println(message) print(tostring(message)) end
|
local function println(message) print(tostring(message)) end
|
||||||
local function print(message) term.write(tostring(message)) end
|
local function print(message) term.write(tostring(message)) end
|
||||||
|
|
||||||
local CCMSI_VERSION = "v1.11a"
|
local CCMSI_VERSION = "v1.11c"
|
||||||
|
|
||||||
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/"
|
||||||
@ -32,6 +32,7 @@ local function red() term.setTextColor(colors.red) end
|
|||||||
local function orange() term.setTextColor(colors.orange) end
|
local function orange() term.setTextColor(colors.orange) end
|
||||||
local function yellow() term.setTextColor(colors.yellow) end
|
local function yellow() term.setTextColor(colors.yellow) end
|
||||||
local function green() term.setTextColor(colors.green) end
|
local function green() term.setTextColor(colors.green) end
|
||||||
|
local function cyan() term.setTextColor(colors.cyan) end
|
||||||
local function blue() term.setTextColor(colors.blue) end
|
local function blue() term.setTextColor(colors.blue) end
|
||||||
local function white() term.setTextColor(colors.white) end
|
local function white() term.setTextColor(colors.white) end
|
||||||
local function lgray() term.setTextColor(colors.lightGray) end
|
local function lgray() term.setTextColor(colors.lightGray) end
|
||||||
@ -199,7 +200,7 @@ if #opts == 0 or opts[1] == "help" then
|
|||||||
println("usage: ccmsi <mode> <app> <branch>")
|
println("usage: ccmsi <mode> <app> <branch>")
|
||||||
println("<mode>")
|
println("<mode>")
|
||||||
lgray()
|
lgray()
|
||||||
println(" check - check latest versions avilable")
|
println(" check - check latest versions available")
|
||||||
yellow()
|
yellow()
|
||||||
println(" ccmsi check <branch> for target")
|
println(" ccmsi check <branch> for target")
|
||||||
lgray()
|
lgray()
|
||||||
@ -266,18 +267,16 @@ if mode == "check" then
|
|||||||
blue();print(local_manifest.versions[key])
|
blue();print(local_manifest.versions[key])
|
||||||
if value ~= local_manifest.versions[key] then
|
if value ~= local_manifest.versions[key] then
|
||||||
white();print(" (")
|
white();print(" (")
|
||||||
term.setTextColor(colors.cyan)
|
cyan();print(value);white();println(" available)")
|
||||||
print(value);white();println(" available)")
|
|
||||||
else green();println(" (up to date)") end
|
else green();println(" (up to date)") end
|
||||||
else
|
else
|
||||||
lgray();print("not installed");white();print(" (latest ")
|
lgray();print("not installed");white();print(" (latest ")
|
||||||
term.setTextColor(colors.cyan)
|
cyan();print(value);white();println(")")
|
||||||
print(value);white();println(")")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if manifest.versions.installer ~= local_manifest.versions.installer then
|
if manifest.versions.installer ~= local_manifest.versions.installer then
|
||||||
yellow();println("\nA newer version of the installer is available, it is recommended to update (use 'ccmsi update installer').");white()
|
yellow();println("\nA different version of the installer is available, it is recommended to update (use 'ccmsi update installer').");white()
|
||||||
end
|
end
|
||||||
elseif mode == "install" or mode == "update" then
|
elseif mode == "install" or mode == "update" then
|
||||||
local update_installer = app == "installer"
|
local update_installer = app == "installer"
|
||||||
@ -315,7 +314,7 @@ elseif mode == "install" or mode == "update" then
|
|||||||
end
|
end
|
||||||
|
|
||||||
if manifest.versions.installer ~= CCMSI_VERSION then
|
if manifest.versions.installer ~= CCMSI_VERSION then
|
||||||
if not update_installer then yellow();println("A newer version of the installer is available, it is recommended to update to it.");white() end
|
if not update_installer then yellow();println("A different version of the installer is available, it is recommended to update to it.");white() end
|
||||||
if update_installer or ask_y_n("Would you like to update now") then
|
if update_installer or ask_y_n("Would you like to update now") then
|
||||||
lgray();println("GET ccmsi.lua")
|
lgray();println("GET ccmsi.lua")
|
||||||
local dl, err = http.get(repo_path .. "ccmsi.lua")
|
local dl, err = http.get(repo_path .. "ccmsi.lua")
|
||||||
|
@ -2,8 +2,8 @@ print("CONFIGURE> SCANNING FOR CONFIGURATOR...")
|
|||||||
|
|
||||||
if fs.exists("reactor-plc/configure.lua") then
|
if fs.exists("reactor-plc/configure.lua") then
|
||||||
require("reactor-plc.configure").configure()
|
require("reactor-plc.configure").configure()
|
||||||
elseif fs.exists("rtu/startup.lua") then
|
elseif fs.exists("rtu/configure.lua") then
|
||||||
print("CONFIGURE> RTU CONFIGURATOR NOT YET IMPLEMENTED IN BETA")
|
require("rtu.configure").configure()
|
||||||
elseif fs.exists("supervisor/startup.lua") then
|
elseif fs.exists("supervisor/startup.lua") then
|
||||||
print("CONFIGURE> SUPERVISOR CONFIGURATOR NOT YET IMPLEMENTED IN BETA")
|
print("CONFIGURE> SUPERVISOR CONFIGURATOR NOT YET IMPLEMENTED IN BETA")
|
||||||
elseif fs.exists("coordinator/startup.lua") then
|
elseif fs.exists("coordinator/startup.lua") then
|
||||||
|
@ -760,20 +760,34 @@ function iocontrol.update_facility_status(status)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- environment detector status
|
-- environment detector status
|
||||||
if type(rtu_statuses.rad_mon) == "table" then
|
if type(rtu_statuses.envds) == "table" then
|
||||||
if #rtu_statuses.rad_mon > 0 then
|
local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false
|
||||||
local rad_mon = rtu_statuses.rad_mon[1]
|
|
||||||
local rtu_faulted = rad_mon[1] ---@type boolean
|
|
||||||
fac.radiation = rad_mon[2] ---@type number
|
|
||||||
|
|
||||||
fac.ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3))
|
for _, envd in pairs(rtu_statuses.envds) do
|
||||||
fac.ps.publish("radiation", fac.radiation)
|
local rtu_faulted = envd[1] ---@type boolean
|
||||||
|
local radiation = envd[2] ---@type radiation_reading
|
||||||
|
local rad_raw = envd[3] ---@type number
|
||||||
|
|
||||||
|
any_conn = true
|
||||||
|
any_faulted = any_faulted or rtu_faulted
|
||||||
|
|
||||||
|
if rad_raw > max_rad then
|
||||||
|
max_rad = rad_raw
|
||||||
|
max_reading = radiation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if any_conn then
|
||||||
|
fac.radiation = max_reading
|
||||||
|
fac.ps.publish("rad_computed_status", util.trinary(any_faulted, 2, 3))
|
||||||
else
|
else
|
||||||
fac.radiation = types.new_zero_radiation_reading()
|
fac.radiation = types.new_zero_radiation_reading()
|
||||||
fac.ps.publish("rad_computed_status", 1)
|
fac.ps.publish("rad_computed_status", 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fac.ps.publish("radiation", fac.radiation)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "radiation monitor list not a table")
|
log.debug(log_header .. "environment detector list not a table")
|
||||||
valid = false
|
valid = false
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@ -1048,16 +1062,28 @@ function iocontrol.update_unit_statuses(statuses)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- environment detector status
|
-- environment detector status
|
||||||
if type(rtu_statuses.rad_mon) == "table" then
|
if type(rtu_statuses.envds) == "table" then
|
||||||
if #rtu_statuses.rad_mon > 0 then
|
local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false
|
||||||
local rad_mon = rtu_statuses.rad_mon[1]
|
|
||||||
-- local rtu_faulted = rad_mon[1] ---@type boolean
|
|
||||||
unit.radiation = rad_mon[2] ---@type number
|
|
||||||
|
|
||||||
unit.unit_ps.publish("radiation", unit.radiation)
|
for _, envd in pairs(rtu_statuses.envds) do
|
||||||
|
local radiation = envd[2] ---@type radiation_reading
|
||||||
|
local rad_raw = envd[3] ---@type number
|
||||||
|
|
||||||
|
any_conn = true
|
||||||
|
|
||||||
|
if rad_raw > max_rad then
|
||||||
|
max_rad = rad_raw
|
||||||
|
max_reading = radiation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if any_conn then
|
||||||
|
unit.radiation = max_reading
|
||||||
else
|
else
|
||||||
unit.radiation = types.new_zero_radiation_reading()
|
unit.radiation = types.new_zero_radiation_reading()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unit.unit_ps.publish("radiation", unit.radiation)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "radiation monitor list not a table")
|
log.debug(log_header .. "radiation monitor list not a table")
|
||||||
valid = false
|
valid = false
|
||||||
|
@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder")
|
|||||||
|
|
||||||
local apisessions = require("coordinator.session.apisessions")
|
local apisessions = require("coordinator.session.apisessions")
|
||||||
|
|
||||||
local COORDINATOR_VERSION = "v1.0.16"
|
local COORDINATOR_VERSION = "v1.0.17"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "2.0.2"
|
core.version = "2.0.7"
|
||||||
|
|
||||||
core.flasher = flasher
|
core.flasher = flasher
|
||||||
core.events = events
|
core.events = events
|
||||||
@ -173,7 +173,7 @@ function core.new_ifield(e, max_len, fg_bg, dis_fg_bg)
|
|||||||
if e.enabled then
|
if e.enabled then
|
||||||
e.w_set_bkg(fg_bg.bkg)
|
e.w_set_bkg(fg_bg.bkg)
|
||||||
e.w_set_fgd(fg_bg.fgd)
|
e.w_set_fgd(fg_bg.fgd)
|
||||||
else
|
elseif dis_fg_bg ~= nil then
|
||||||
e.w_set_bkg(dis_fg_bg.bkg)
|
e.w_set_bkg(dis_fg_bg.bkg)
|
||||||
e.w_set_fgd(dis_fg_bg.fgd)
|
e.w_set_fgd(dis_fg_bg.fgd)
|
||||||
end
|
end
|
||||||
|
@ -91,6 +91,8 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
p_window = nil, ---@type table
|
p_window = nil, ---@type table
|
||||||
position = events.new_coord_2d(1, 1),
|
position = events.new_coord_2d(1, 1),
|
||||||
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
|
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
|
||||||
|
offset_x = 0,
|
||||||
|
offset_y = 0,
|
||||||
next_y = 1, -- next child y coordinate
|
next_y = 1, -- next child y coordinate
|
||||||
next_id = 0, -- next child ID
|
next_id = 0, -- next child ID
|
||||||
subscriptions = {},
|
subscriptions = {},
|
||||||
@ -105,6 +107,7 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
value = nil, ---@type any
|
value = nil, ---@type any
|
||||||
window = nil, ---@type table
|
window = nil, ---@type table
|
||||||
content_window = nil, ---@type table|nil
|
content_window = nil, ---@type table|nil
|
||||||
|
mouse_window_shift = { x = 0, y = 0 },
|
||||||
fg_bg = core.cpair(colors.white, colors.black),
|
fg_bg = core.cpair(colors.white, colors.black),
|
||||||
frame = core.gframe(1, 1, 1, 1),
|
frame = core.gframe(1, 1, 1, 1),
|
||||||
children = {},
|
children = {},
|
||||||
@ -193,6 +196,10 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
---@param offset_y integer y offset for mouse events
|
---@param offset_y integer y offset for mouse events
|
||||||
---@param next_y integer next line if no y was provided
|
---@param next_y integer next line if no y was provided
|
||||||
function protected.prepare_template(offset_x, offset_y, next_y)
|
function protected.prepare_template(offset_x, offset_y, next_y)
|
||||||
|
-- record offsets in case there is a reposition
|
||||||
|
self.offset_x = offset_x
|
||||||
|
self.offset_y = offset_y
|
||||||
|
|
||||||
-- get frame coordinates/size
|
-- get frame coordinates/size
|
||||||
if args.gframe ~= nil then
|
if args.gframe ~= nil then
|
||||||
protected.frame.x = args.gframe.x
|
protected.frame.x = args.gframe.x
|
||||||
@ -344,6 +351,10 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
-- handle this element having been unfocused
|
-- handle this element having been unfocused
|
||||||
function protected.on_unfocused() end
|
function protected.on_unfocused() end
|
||||||
|
|
||||||
|
-- handle this element having had a child focused
|
||||||
|
---@param child graphics_element
|
||||||
|
function protected.on_child_focused(child) end
|
||||||
|
|
||||||
-- handle this element having been shown
|
-- handle this element having been shown
|
||||||
function protected.on_shown() end
|
function protected.on_shown() end
|
||||||
|
|
||||||
@ -520,6 +531,13 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
else args.parent.__focus_child(child) end
|
else args.parent.__focus_child(child) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- a child was focused, used to make sure it is actually visible to the user in the content frame
|
||||||
|
---@param child graphics_element
|
||||||
|
function public.__child_focused(child)
|
||||||
|
protected.on_child_focused(child)
|
||||||
|
if not self.is_root then args.parent.__child_focused(public) end
|
||||||
|
end
|
||||||
|
|
||||||
-- get a child element
|
-- get a child element
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param id element_id
|
---@param id element_id
|
||||||
@ -652,6 +670,7 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
if args.can_focus and protected.enabled and not self.focused then
|
if args.can_focus and protected.enabled and not self.focused then
|
||||||
self.focused = true
|
self.focused = true
|
||||||
protected.on_focused()
|
protected.on_focused()
|
||||||
|
if not self.is_root then args.parent.__child_focused(public) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -666,7 +685,7 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
-- unfocus this element and all its children
|
-- unfocus this element and all its children
|
||||||
function public.unfocus_all()
|
function public.unfocus_all()
|
||||||
public.unfocus()
|
public.unfocus()
|
||||||
for _, child in pairs(protected.children) do child.get().unfocus() end
|
for _, child in pairs(protected.children) do child.get().unfocus_all() end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- custom recolor command, varies by element if implemented
|
-- custom recolor command, varies by element if implemented
|
||||||
@ -681,7 +700,22 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
-- offsets relative to parent frame are where (1, 1) would be on top of the parent's top left corner
|
-- offsets relative to parent frame are where (1, 1) would be on top of the parent's top left corner
|
||||||
---@param x integer x position relative to parent frame
|
---@param x integer x position relative to parent frame
|
||||||
---@param y integer y position relative to parent frame
|
---@param y integer y position relative to parent frame
|
||||||
function public.reposition(x, y) protected.window.reposition(x, y) end
|
function public.reposition(x, y)
|
||||||
|
protected.window.reposition(x, y)
|
||||||
|
|
||||||
|
-- record position
|
||||||
|
self.position.x, self.position.y = protected.window.getPosition()
|
||||||
|
|
||||||
|
-- shift per parent child offset
|
||||||
|
self.position.x = self.position.x + self.offset_x
|
||||||
|
self.position.y = self.position.y + self.offset_y
|
||||||
|
|
||||||
|
-- calculate mouse event bounds
|
||||||
|
self.bounds.x1 = self.position.x
|
||||||
|
self.bounds.x2 = self.position.x + protected.frame.w - 1
|
||||||
|
self.bounds.y1 = self.position.y
|
||||||
|
self.bounds.y2 = self.position.y + protected.frame.h - 1
|
||||||
|
end
|
||||||
|
|
||||||
-- FUNCTION CALLBACKS --
|
-- FUNCTION CALLBACKS --
|
||||||
|
|
||||||
@ -704,10 +738,11 @@ function element.new(args, child_offset_x, child_offset_y)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local event_T = events.mouse_transposed(event, self.position.x, self.position.y)
|
local event_T = events.mouse_transposed(event, self.position.x, self.position.y)
|
||||||
|
|
||||||
-- handle the mouse event then pass to children
|
|
||||||
protected.handle_mouse(event_T)
|
protected.handle_mouse(event_T)
|
||||||
for _, child in pairs(protected.children) do child.get().handle_mouse(event_T) end
|
|
||||||
|
-- shift child event if the content window has moved then pass to children
|
||||||
|
local c_event_T = events.mouse_transposed(event_T, protected.mouse_window_shift.x + 1, protected.mouse_window_shift.y + 1)
|
||||||
|
for _, child in pairs(protected.children) do child.get().handle_mouse(c_event_T) end
|
||||||
elseif event.type == events.MOUSE_CLICK.DOWN or event.type == events.MOUSE_CLICK.TAP then
|
elseif event.type == events.MOUSE_CLICK.DOWN or event.type == events.MOUSE_CLICK.TAP then
|
||||||
-- clicked out, unfocus this element and children
|
-- clicked out, unfocus this element and children
|
||||||
public.unfocus_all()
|
public.unfocus_all()
|
||||||
|
@ -5,6 +5,8 @@ local tcd = require("scada-common.tcd")
|
|||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
local element = require("graphics.element")
|
local element = require("graphics.element")
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
||||||
local KEY_CLICK = core.events.KEY_CLICK
|
local KEY_CLICK = core.events.KEY_CLICK
|
||||||
|
|
||||||
@ -12,6 +14,7 @@ local KEY_CLICK = core.events.KEY_CLICK
|
|||||||
---@field text string button text
|
---@field text string button text
|
||||||
---@field callback function function to call on touch
|
---@field callback function function to call on touch
|
||||||
---@field min_width? integer text length if omitted
|
---@field min_width? integer text length if omitted
|
||||||
|
---@field alignment? ALIGN text align if min width > length
|
||||||
---@field active_fg_bg? cpair foreground/background colors when pressed
|
---@field active_fg_bg? cpair foreground/background colors when pressed
|
||||||
---@field dis_fg_bg? cpair foreground/background colors when disabled
|
---@field dis_fg_bg? cpair foreground/background colors when disabled
|
||||||
---@field parent graphics_element
|
---@field parent graphics_element
|
||||||
@ -31,6 +34,7 @@ local function push_button(args)
|
|||||||
element.assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "min_width must be nil or a number > 0")
|
element.assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "min_width must be nil or a number > 0")
|
||||||
|
|
||||||
local text_width = string.len(args.text)
|
local text_width = string.len(args.text)
|
||||||
|
local alignment = args.alignment or ALIGN.CENTER
|
||||||
|
|
||||||
-- set automatic settings
|
-- set automatic settings
|
||||||
args.can_focus = true
|
args.can_focus = true
|
||||||
@ -41,9 +45,15 @@ local function push_button(args)
|
|||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args)
|
local e = element.new(args)
|
||||||
|
|
||||||
local h_pad = math.floor((e.frame.w - text_width) / 2) + 1
|
local h_pad = 1
|
||||||
local v_pad = math.floor(e.frame.h / 2) + 1
|
local v_pad = math.floor(e.frame.h / 2) + 1
|
||||||
|
|
||||||
|
if alignment == ALIGN.CENTER then
|
||||||
|
h_pad = math.floor((e.frame.w - text_width) / 2) + 1
|
||||||
|
elseif alignment == ALIGN.RIGHT then
|
||||||
|
h_pad = (e.frame.w - text_width) + 1
|
||||||
|
end
|
||||||
|
|
||||||
-- draw the button
|
-- draw the button
|
||||||
function e.redraw()
|
function e.redraw()
|
||||||
e.window.clear()
|
e.window.clear()
|
||||||
|
@ -133,6 +133,9 @@ local function number_field(args)
|
|||||||
elseif type(args.min) == "number" and val < min then
|
elseif type(args.min) == "number" and val < min then
|
||||||
e.value = "" .. min
|
e.value = "" .. min
|
||||||
ifield.nav_start()
|
ifield.nav_start()
|
||||||
|
else
|
||||||
|
e.value = "" .. val
|
||||||
|
ifield.nav_end()
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
e.value = ""
|
e.value = ""
|
||||||
|
@ -5,6 +5,7 @@ local tcd = require("scada-common.tcd")
|
|||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
local element = require("graphics.element")
|
local element = require("graphics.element")
|
||||||
|
|
||||||
|
local KEY_CLICK = core.events.KEY_CLICK
|
||||||
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
||||||
|
|
||||||
---@class listbox_args
|
---@class listbox_args
|
||||||
@ -33,6 +34,8 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
|
|||||||
---@param args listbox_args
|
---@param args listbox_args
|
||||||
---@return graphics_element element, element_id id
|
---@return graphics_element element, element_id id
|
||||||
local function listbox(args)
|
local function listbox(args)
|
||||||
|
args.can_focus = true
|
||||||
|
|
||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args)
|
local e = element.new(args)
|
||||||
|
|
||||||
@ -128,7 +131,7 @@ local function listbox(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
e.w_set_cur(e.frame.w, i)
|
e.w_set_cur(e.frame.w, i)
|
||||||
e.w_write(" ")
|
if e.is_focused() then e.w_write("\x7f") else e.w_write(" ") end
|
||||||
end
|
end
|
||||||
|
|
||||||
e.w_set_bkg(e.fg_bg.bkg)
|
e.w_set_bkg(e.fg_bg.bkg)
|
||||||
@ -158,6 +161,9 @@ local function listbox(args)
|
|||||||
scroll_frame.reposition(1, 1 + scroll_offset)
|
scroll_frame.reposition(1, 1 + scroll_offset)
|
||||||
scroll_frame.setVisible(true)
|
scroll_frame.setVisible(true)
|
||||||
|
|
||||||
|
-- shift mouse events
|
||||||
|
e.mouse_window_shift.y = scroll_offset
|
||||||
|
|
||||||
draw_bar()
|
draw_bar()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -219,6 +225,32 @@ local function listbox(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- handle focus
|
||||||
|
e.on_focused = draw_bar
|
||||||
|
e.on_unfocused = draw_bar
|
||||||
|
|
||||||
|
-- handle a child in the list being focused, make sure it is visible
|
||||||
|
function e.on_child_focused(child)
|
||||||
|
for i = 1, #list do
|
||||||
|
local item = list[i] ---@type listbox_item
|
||||||
|
if item.e == child then
|
||||||
|
if (item.y + scroll_offset) <= 0 then
|
||||||
|
scroll_offset = 1 - item.y
|
||||||
|
update_positions()
|
||||||
|
draw_bar()
|
||||||
|
elseif (item.y + scroll_offset) == 1 then
|
||||||
|
-- do nothing, it's right at the top (if the bottom doesn't fit we can't easily fix that)
|
||||||
|
elseif ((item.h + item.y - 1) + scroll_offset) > e.frame.h then
|
||||||
|
scroll_offset = 1 - ((item.h + item.y) - e.frame.h)
|
||||||
|
update_positions()
|
||||||
|
draw_bar()
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- handle mouse interaction
|
-- handle mouse interaction
|
||||||
---@param event mouse_interaction mouse event
|
---@param event mouse_interaction mouse event
|
||||||
function e.handle_mouse(event)
|
function e.handle_mouse(event)
|
||||||
@ -226,23 +258,27 @@ local function listbox(args)
|
|||||||
if event.type == MOUSE_CLICK.TAP then
|
if event.type == MOUSE_CLICK.TAP then
|
||||||
if event.current.x == e.frame.w then
|
if event.current.x == e.frame.w then
|
||||||
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
||||||
draw_arrows(1)
|
|
||||||
scroll_up()
|
scroll_up()
|
||||||
|
if event.current.y == 1 then
|
||||||
|
draw_arrows(1)
|
||||||
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
||||||
|
end
|
||||||
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
||||||
draw_arrows(-1)
|
|
||||||
scroll_down()
|
scroll_down()
|
||||||
|
if event.current.y == e.frame.h then
|
||||||
|
draw_arrows(-1)
|
||||||
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
elseif event.type == MOUSE_CLICK.DOWN then
|
elseif event.type == MOUSE_CLICK.DOWN then
|
||||||
if event.current.x == e.frame.w then
|
if event.current.x == e.frame.w then
|
||||||
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
||||||
draw_arrows(1)
|
|
||||||
scroll_up()
|
scroll_up()
|
||||||
|
if event.current.y == 1 then draw_arrows(1) end
|
||||||
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
||||||
draw_arrows(-1)
|
|
||||||
scroll_down()
|
scroll_down()
|
||||||
|
if event.current.y == e.frame.h then draw_arrows(-1) end
|
||||||
else
|
else
|
||||||
-- clicked on bar
|
-- clicked on bar
|
||||||
holding_bar = true
|
holding_bar = true
|
||||||
@ -274,6 +310,24 @@ local function listbox(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- handle keyboard interaction
|
||||||
|
---@param event key_interaction key event
|
||||||
|
function e.handle_key(event)
|
||||||
|
if event.type == KEY_CLICK.DOWN or event.type == KEY_CLICK.HELD then
|
||||||
|
if event.key == keys.up then
|
||||||
|
scroll_up()
|
||||||
|
elseif event.key == keys.down then
|
||||||
|
scroll_down()
|
||||||
|
elseif event.key == keys.home then
|
||||||
|
scroll_offset = 0
|
||||||
|
update_positions()
|
||||||
|
elseif event.key == keys["end"] then
|
||||||
|
scroll_offset = max_down_scroll
|
||||||
|
update_positions()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- element redraw
|
-- element redraw
|
||||||
function e.redraw()
|
function e.redraw()
|
||||||
draw_arrows(0)
|
draw_arrows(0)
|
||||||
|
2
imgen.py
2
imgen.py
@ -60,7 +60,7 @@ def make_manifest(size):
|
|||||||
},
|
},
|
||||||
"files" : {
|
"files" : {
|
||||||
# common files
|
# common files
|
||||||
"system" : [ "initenv.lua", "startup.lua", "configure.lua" ],
|
"system" : [ "initenv.lua", "startup.lua", "configure.lua", "LICENSE" ],
|
||||||
"common" : list_files("./scada-common"),
|
"common" : list_files("./scada-common"),
|
||||||
"graphics" : list_files("./graphics"),
|
"graphics" : list_files("./graphics"),
|
||||||
"lockbox" : list_files("./lockbox"),
|
"lockbox" : list_files("./lockbox"),
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local tcd = require("scada-common.tcd")
|
local tcd = require("scada-common.tcd")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
local rsio = require("scada-common.rsio")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ local NumberField = require("graphics.elements.form.number_field")
|
|||||||
local TextField = require("graphics.elements.form.text_field")
|
local TextField = require("graphics.elements.form.text_field")
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
|
local tri = util.trinary
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
@ -104,21 +106,24 @@ local tmp_cfg = {
|
|||||||
|
|
||||||
---@class plc_config
|
---@class plc_config
|
||||||
local ini_cfg = {}
|
local ini_cfg = {}
|
||||||
|
---@class plc_config
|
||||||
|
local settings_cfg = {}
|
||||||
|
|
||||||
|
-- all settings fields, their nice names, and their default values
|
||||||
local fields = {
|
local fields = {
|
||||||
{ "Networked", "Networked" },
|
{ "Networked", "Networked", false },
|
||||||
{ "UnitID", "Unit ID" },
|
{ "UnitID", "Unit ID", 1 },
|
||||||
{ "EmerCoolEnable", "Emergency Coolant" },
|
{ "EmerCoolEnable", "Emergency Coolant", false },
|
||||||
{ "EmerCoolSide", "Emergency Coolant Side" },
|
{ "EmerCoolSide", "Emergency Coolant Side", nil },
|
||||||
{ "EmerCoolColor", "Emergency Coolant Color" },
|
{ "EmerCoolColor", "Emergency Coolant Color", nil },
|
||||||
{ "SVR_Channel", "SVR Channel" },
|
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||||
{ "PLC_Channel", "PLC Channel" },
|
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||||
{ "ConnTimeout", "Connection Timeout" },
|
{ "ConnTimeout", "Connection Timeout", 5 },
|
||||||
{ "TrustedRange", "Trusted Range" },
|
{ "TrustedRange", "Trusted Range", 0 },
|
||||||
{ "AuthKey", "Facility Auth Key" },
|
{ "AuthKey", "Facility Auth Key" , ""},
|
||||||
{ "LogMode", "Log Mode" },
|
{ "LogMode", "Log Mode", log.MODE.APPEND },
|
||||||
{ "LogPath", "Log Path" },
|
{ "LogPath", "Log Path", "/log.txt" },
|
||||||
{ "LogDebug","Log Debug Messages" }
|
{ "LogDebug","Log Debug Messages", false }
|
||||||
}
|
}
|
||||||
|
|
||||||
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" }
|
||||||
@ -126,25 +131,6 @@ local side_options_map = { "top", "bottom", "left", "right", "front", "back" }
|
|||||||
local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" }
|
local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" }
|
||||||
local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown }
|
local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown }
|
||||||
|
|
||||||
local color_name_map = {
|
|
||||||
[colors.red] = "red",
|
|
||||||
[colors.orange] = "orange",
|
|
||||||
[colors.yellow] = "yellow",
|
|
||||||
[colors.lime] = "lime",
|
|
||||||
[colors.green] = "green",
|
|
||||||
[colors.cyan] = "cyan",
|
|
||||||
[colors.lightBlue] = "lightBlue",
|
|
||||||
[colors.blue] = "blue",
|
|
||||||
[colors.purple] = "purple",
|
|
||||||
[colors.magenta] = "magenta",
|
|
||||||
[colors.pink] = "pink",
|
|
||||||
[colors.white] = "white",
|
|
||||||
[colors.lightGray] = "lightGray",
|
|
||||||
[colors.gray] = "gray",
|
|
||||||
[colors.black] = "black",
|
|
||||||
[colors.brown] = "brown"
|
|
||||||
}
|
|
||||||
|
|
||||||
-- convert text representation to index
|
-- convert text representation to index
|
||||||
---@param side string
|
---@param side string
|
||||||
local function side_to_idx(side)
|
local function side_to_idx(side)
|
||||||
@ -163,20 +149,15 @@ end
|
|||||||
|
|
||||||
-- load data from the settings file
|
-- load data from the settings file
|
||||||
---@param target plc_config
|
---@param target plc_config
|
||||||
local function load_settings(target)
|
---@param raw boolean? true to not use default values
|
||||||
target.Networked = settings.get("Networked", false)
|
local function load_settings(target, raw)
|
||||||
target.UnitID = settings.get("UnitID", 1)
|
for _, v in pairs(fields) do settings.unset(v[1]) end
|
||||||
target.EmerCoolEnable = settings.get("EmerCoolEnable", false)
|
|
||||||
target.EmerCoolSide = settings.get("EmerCoolSide", nil)
|
local loaded = settings.load("/reactor-plc.settings")
|
||||||
target.EmerCoolColor = settings.get("EmerCoolColor", nil)
|
|
||||||
target.SVR_Channel = settings.get("SVR_Channel", 16240)
|
for _, v in pairs(fields) do target[v[1]] = settings.get(v[1], tri(raw, nil, v[3])) end
|
||||||
target.PLC_Channel = settings.get("PLC_Channel", 16241)
|
|
||||||
target.ConnTimeout = settings.get("ConnTimeout", 5)
|
return loaded
|
||||||
target.TrustedRange = settings.get("TrustedRange", 0)
|
|
||||||
target.AuthKey = settings.get("AuthKey", "")
|
|
||||||
target.LogMode = settings.get("LogMode", log.MODE.APPEND)
|
|
||||||
target.LogPath = settings.get("LogPath", "/log.txt")
|
|
||||||
target.LogDebug = settings.get("LogDebug", false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create the config view
|
-- create the config view
|
||||||
@ -203,16 +184,16 @@ local function config_view(display)
|
|||||||
|
|
||||||
local y_start = 5
|
local y_start = 5
|
||||||
|
|
||||||
TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the Reactor PLC configurator! Please select one of the following options."}
|
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Reactor PLC configurator! Please select one of the following options."}
|
||||||
|
|
||||||
if tool_ctl.ask_config then
|
if tool_ctl.ask_config then
|
||||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text_align=CENTER,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||||
y_start = y_start + 5
|
y_start = y_start + 5
|
||||||
end
|
end
|
||||||
|
|
||||||
local function view_config()
|
local function view_config()
|
||||||
tool_ctl.viewing_config = true
|
tool_ctl.viewing_config = true
|
||||||
tool_ctl.gen_summary(ini_cfg)
|
tool_ctl.gen_summary(settings_cfg)
|
||||||
tool_ctl.settings_apply.hide(true)
|
tool_ctl.settings_apply.hide(true)
|
||||||
main_pane.set_value(5)
|
main_pane.set_value(5)
|
||||||
end
|
end
|
||||||
@ -239,10 +220,10 @@ local function config_view(display)
|
|||||||
|
|
||||||
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4}}
|
local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4}}
|
||||||
|
|
||||||
TextBox{parent=plc_cfg,x=1,y=2,height=1,text_align=CENTER,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
TextBox{parent=plc_cfg,x=1,y=2,height=1,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)}
|
||||||
|
|
||||||
TextBox{parent=plc_c_1,x=1,y=1,height=1,text_align=CENTER,text="Would you like to set this PLC as networked?"}
|
TextBox{parent=plc_c_1,x=1,y=1,height=1,text="Would you like to set this PLC as networked?"}
|
||||||
TextBox{parent=plc_c_1,x=1,y=3,height=4,text_align=CENTER,text="If you have a supervisor, select the box. You will later be prompted to select the network configuration. If you instead want to use this as a standalone safety system, don't select the box.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=plc_c_1,x=1,y=3,height=4,text="If you have a supervisor, select the box. You will later be prompted to select the network configuration. If you instead want to use this as a standalone safety system, don't select the box.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local networked = CheckBox{parent=plc_c_1,x=1,y=8,label="Networked",default=ini_cfg.Networked,box_fg_bg=cpair(colors.orange,colors.black)}
|
local networked = CheckBox{parent=plc_c_1,x=1,y=8,label="Networked",default=ini_cfg.Networked,box_fg_bg=cpair(colors.orange,colors.black)}
|
||||||
|
|
||||||
@ -254,13 +235,13 @@ local function config_view(display)
|
|||||||
PushButton{parent=plc_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=plc_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_networked,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_networked,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=plc_c_2,x=1,y=1,height=1,text_align=CENTER,text="Please enter the reactor unit ID for this PLC."}
|
TextBox{parent=plc_c_2,x=1,y=1,height=1,text="Please enter the reactor unit ID for this PLC."}
|
||||||
TextBox{parent=plc_c_2,x=1,y=3,height=3,text_align=CENTER,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=plc_c_2,x=1,y=3,height=3,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=plc_c_2,x=1,y=6,height=1,text_align=CENTER,text="Unit #"}
|
TextBox{parent=plc_c_2,x=1,y=6,height=1,text="Unit #"}
|
||||||
local u_id = NumberField{parent=plc_c_2,x=7,y=6,width=5,max_digits=3,default=ini_cfg.UnitID,min=1,fg_bg=bw_fg_bg}
|
local u_id = NumberField{parent=plc_c_2,x=7,y=6,width=5,max_digits=3,default=ini_cfg.UnitID,min=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local u_id_err = TextBox{parent=plc_c_2,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please set a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local u_id_err = TextBox{parent=plc_c_2,x=8,y=14,height=1,width=35,text="Please set a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_id()
|
local function submit_id()
|
||||||
local unit_id = tonumber(u_id.get_value())
|
local unit_id = tonumber(u_id.get_value())
|
||||||
@ -274,8 +255,8 @@ local function config_view(display)
|
|||||||
PushButton{parent=plc_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=plc_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_id,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_id,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=plc_c_3,x=1,y=1,height=4,text_align=CENTER,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC. "}
|
TextBox{parent=plc_c_3,x=1,y=1,height=4,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC. "}
|
||||||
TextBox{parent=plc_c_3,x=1,y=6,height=5,text_align=CENTER,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=plc_c_3,x=1,y=6,height=5,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local en_em_cool = CheckBox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)}
|
local en_em_cool = CheckBox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)}
|
||||||
|
|
||||||
@ -291,17 +272,17 @@ local function config_view(display)
|
|||||||
PushButton{parent=plc_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=plc_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_en_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=plc_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_en_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=plc_c_4,x=1,y=1,height=1,text_align=CENTER,text="Emergency Coolant Redstone Output Side"}
|
TextBox{parent=plc_c_4,x=1,y=1,height=1,text="Emergency Coolant Redstone Output Side"}
|
||||||
local side = Radio2D{parent=plc_c_4,x=1,y=2,rows=2,columns=3,default=side_to_idx(ini_cfg.EmerCoolSide),options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.orange}
|
local side = Radio2D{parent=plc_c_4,x=1,y=2,rows=2,columns=3,default=side_to_idx(ini_cfg.EmerCoolSide),options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.orange}
|
||||||
|
|
||||||
TextBox{parent=plc_c_4,x=1,y=5,height=1,text_align=CENTER,text="Bundled Redstone Configuration"}
|
TextBox{parent=plc_c_4,x=1,y=5,height=1,text="Bundled Redstone Configuration"}
|
||||||
local bundled = CheckBox{parent=plc_c_4,x=1,y=6,label="Is Bundled?",default=ini_cfg.EmerCoolColor~=nil,box_fg_bg=cpair(colors.orange,colors.black),callback=function(v)tool_ctl.bundled_emcool(v)end}
|
local bundled = CheckBox{parent=plc_c_4,x=1,y=6,label="Is Bundled?",default=ini_cfg.EmerCoolColor~=nil,box_fg_bg=cpair(colors.orange,colors.black),callback=function(v)tool_ctl.bundled_emcool(v)end}
|
||||||
local color = Radio2D{parent=plc_c_4,x=1,y=8,rows=4,columns=4,default=color_to_idx(ini_cfg.EmerCoolColor),options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
local color = Radio2D{parent=plc_c_4,x=1,y=8,rows=4,columns=4,default=color_to_idx(ini_cfg.EmerCoolColor),options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||||
if ini_cfg.EmerCoolColor == nil then color.disable() end
|
if ini_cfg.EmerCoolColor == nil then color.disable() end
|
||||||
|
|
||||||
local function submit_emcool()
|
local function submit_emcool()
|
||||||
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
tmp_cfg.EmerCoolSide = side_options_map[side.get_value()]
|
||||||
tmp_cfg.EmerCoolColor = color_options_map[color.get_value()]
|
tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil)
|
||||||
next_from_plc()
|
next_from_plc()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -316,19 +297,19 @@ local function config_view(display)
|
|||||||
|
|
||||||
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}}
|
||||||
|
|
||||||
TextBox{parent=net_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please set the network channels below."}
|
TextBox{parent=net_c_1,x=1,y=1,height=1,text="Please set the network channels below."}
|
||||||
TextBox{parent=net_c_1,x=1,y=3,height=4,text_align=CENTER,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_1,x=1,y=8,height=1,text_align=CENTER,text="Supervisor Channel"}
|
TextBox{parent=net_c_1,x=1,y=8,height=1,text="Supervisor Channel"}
|
||||||
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=9,y=9,height=4,text_align=CENTER,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=1,y=11,height=1,text_align=CENTER,text="PLC Channel"}
|
TextBox{parent=net_c_1,x=1,y=11,height=1,text="PLC Channel"}
|
||||||
local plc_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
local plc_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_1,x=9,y=12,height=4,text_align=CENTER,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_1,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_channels()
|
local function submit_channels()
|
||||||
local svr_c = tonumber(svr_chan.get_value())
|
local svr_c = tonumber(svr_chan.get_value())
|
||||||
@ -350,16 +331,16 @@ local function config_view(display)
|
|||||||
PushButton{parent=net_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=1,height=1,text_align=CENTER,text="Connection Timeout"}
|
TextBox{parent=net_c_2,x=1,y=1,height=1,text="Connection Timeout"}
|
||||||
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,fg_bg=bw_fg_bg}
|
local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=9,y=2,height=2,text_align=CENTER,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=1,y=3,height=4,text_align=CENTER,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_2,x=1,y=8,height=1,text_align=CENTER,text="Trusted Range"}
|
TextBox{parent=net_c_2,x=1,y=8,height=1,text="Trusted Range"}
|
||||||
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_digits=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_digits=20,allow_decimal=true,fg_bg=bw_fg_bg}
|
||||||
TextBox{parent=net_c_2,x=1,y=10,height=4,text_align=CENTER,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_ct_tr()
|
local function submit_ct_tr()
|
||||||
local timeout_val = tonumber(timeout.get_value())
|
local timeout_val = tonumber(timeout.get_value())
|
||||||
@ -381,10 +362,10 @@ local function config_view(display)
|
|||||||
PushButton{parent=net_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=net_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=net_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=1,height=2,text_align=CENTER,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
|
||||||
TextBox{parent=net_c_3,x=1,y=4,height=6,text_align=CENTER,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=net_c_3,x=1,y=11,height=1,text_align=CENTER,text="Facility Auth Key"}
|
TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"}
|
||||||
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end
|
||||||
@ -394,7 +375,7 @@ local function config_view(display)
|
|||||||
hide_key.set_value(true)
|
hide_key.set_value(true)
|
||||||
censor_key(true)
|
censor_key(true)
|
||||||
|
|
||||||
local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_auth()
|
local function submit_auth()
|
||||||
local v = key.get_value()
|
local v = key.get_value()
|
||||||
@ -412,20 +393,20 @@ local function config_view(display)
|
|||||||
|
|
||||||
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=log_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
TextBox{parent=log_cfg,x=1,y=2,height=1,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please configure logging below."}
|
TextBox{parent=log_c_1,x=1,y=1,height=1,text="Please configure logging below."}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=3,height=1,text_align=CENTER,text="Log File Mode"}
|
TextBox{parent=log_c_1,x=1,y=3,height=1,text="Log File Mode"}
|
||||||
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink}
|
||||||
|
|
||||||
TextBox{parent=log_c_1,x=1,y=7,height=1,text_align=CENTER,text="Log File Path"}
|
TextBox{parent=log_c_1,x=1,y=7,height=1,text="Log File Path"}
|
||||||
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)}
|
||||||
TextBox{parent=log_c_1,x=3,y=11,height=2,text_align=CENTER,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg}
|
||||||
|
|
||||||
local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||||
|
|
||||||
local function submit_log()
|
local function submit_log()
|
||||||
if path.get_value() ~= "" then
|
if path.get_value() ~= "" then
|
||||||
@ -457,7 +438,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}}
|
||||||
|
|
||||||
TextBox{parent=summary,x=1,y=2,height=1,text_align=CENTER,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
TextBox{parent=summary,x=1,y=2,height=1,text=" Summary",fg_bg=cpair(colors.black,colors.green)}
|
||||||
|
|
||||||
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
@ -483,6 +464,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
if settings.save("reactor-plc.settings") then
|
if settings.save("reactor-plc.settings") then
|
||||||
load_settings(ini_cfg)
|
load_settings(ini_cfg)
|
||||||
|
load_settings(settings_cfg, true)
|
||||||
|
|
||||||
try_set(networked, ini_cfg.Networked)
|
try_set(networked, ini_cfg.Networked)
|
||||||
try_set(u_id, ini_cfg.UnitID)
|
try_set(u_id, ini_cfg.UnitID)
|
||||||
@ -499,6 +481,8 @@ local function config_view(display)
|
|||||||
try_set(path, ini_cfg.LogPath)
|
try_set(path, ini_cfg.LogPath)
|
||||||
try_set(en_dbg, ini_cfg.LogDebug)
|
try_set(en_dbg, ini_cfg.LogDebug)
|
||||||
|
|
||||||
|
tool_ctl.view_cfg.enable()
|
||||||
|
|
||||||
if tool_ctl.importing_legacy then
|
if tool_ctl.importing_legacy then
|
||||||
tool_ctl.importing_legacy = false
|
tool_ctl.importing_legacy = false
|
||||||
sum_pane.set_value(3)
|
sum_pane.set_value(3)
|
||||||
@ -514,7 +498,7 @@ local function config_view(display)
|
|||||||
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)}
|
||||||
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||||
|
|
||||||
TextBox{parent=sum_c_2,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"}
|
TextBox{parent=sum_c_2,x=1,y=1,height=1,text="Settings saved!"}
|
||||||
|
|
||||||
local function go_home()
|
local function go_home()
|
||||||
main_pane.set_value(1)
|
main_pane.set_value(1)
|
||||||
@ -526,7 +510,7 @@ local function config_view(display)
|
|||||||
PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
TextBox{parent=sum_c_3,x=1,y=1,height=2,text_align=CENTER,text="The old config.lua file will now be deleted, then the configurator will exit."}
|
TextBox{parent=sum_c_3,x=1,y=1,height=2,text="The old config.lua file will now be deleted, then the configurator will exit."}
|
||||||
|
|
||||||
local function delete_legacy()
|
local function delete_legacy()
|
||||||
fs.delete("/reactor-plc/config.lua")
|
fs.delete("/reactor-plc/config.lua")
|
||||||
@ -536,8 +520,7 @@ local function config_view(display)
|
|||||||
PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)}
|
PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
TextBox{parent=sum_c_4,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
TextBox{parent=sum_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||||
|
|
||||||
PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||||
PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
@ -545,7 +528,7 @@ local function config_view(display)
|
|||||||
|
|
||||||
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
local cl = Div{parent=changelog,x=2,y=4,width=49}
|
||||||
|
|
||||||
TextBox{parent=changelog,x=1,y=2,height=1,text_align=CENTER,text=" Config Change Log",fg_bg=bw_fg_bg}
|
TextBox{parent=changelog,x=1,y=2,height=1,text=" Config Change Log",fg_bg=bw_fg_bg}
|
||||||
|
|
||||||
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||||
|
|
||||||
@ -627,8 +610,8 @@ local function config_view(display)
|
|||||||
|
|
||||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end
|
if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end
|
||||||
if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end
|
if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end
|
||||||
if f[1] == "EmerCoolColor" and raw ~= nil then val = color_name_map[raw] end
|
if f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw) end
|
||||||
if val == "nil" then val = "n/a" end
|
if val == "nil" then val = "<not set>" end
|
||||||
|
|
||||||
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||||
alternate = not alternate
|
alternate = not alternate
|
||||||
@ -665,9 +648,8 @@ end
|
|||||||
---@param ask_config? boolean indicate if this is being called by the PLC startup app due to an invalid configuration
|
---@param ask_config? boolean indicate if this is being called by the PLC startup app due to an invalid configuration
|
||||||
function configurator.configure(ask_config)
|
function configurator.configure(ask_config)
|
||||||
tool_ctl.ask_config = ask_config == true
|
tool_ctl.ask_config = ask_config == true
|
||||||
tool_ctl.has_config = settings.load("/reactor-plc.settings")
|
tool_ctl.has_config = load_settings(ini_cfg)
|
||||||
|
load_settings(settings_cfg, true)
|
||||||
load_settings(ini_cfg)
|
|
||||||
|
|
||||||
reset_term()
|
reset_term()
|
||||||
|
|
||||||
@ -685,18 +667,14 @@ function configurator.configure(ask_config)
|
|||||||
|
|
||||||
-- handle event
|
-- handle event
|
||||||
if event == "timer" then
|
if event == "timer" then
|
||||||
-- notify timer callback dispatcher
|
|
||||||
tcd.handle(param1)
|
tcd.handle(param1)
|
||||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then
|
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
|
|
||||||
local m_e = core.events.new_mouse_event(event, param1, param2, param3)
|
local m_e = core.events.new_mouse_event(event, param1, param2, param3)
|
||||||
if m_e then display.handle_mouse(m_e) end
|
if m_e then display.handle_mouse(m_e) end
|
||||||
elseif event == "char" or event == "key" or event == "key_up" then
|
elseif event == "char" or event == "key" or event == "key_up" then
|
||||||
-- handle a key event
|
|
||||||
local k_e = core.events.new_key_event(event, param1, param2)
|
local k_e = core.events.new_key_event(event, param1, param2)
|
||||||
if k_e then display.handle_key(k_e) end
|
if k_e then display.handle_key(k_e) end
|
||||||
elseif event == "paste" then
|
elseif event == "paste" then
|
||||||
-- handle a paste event
|
|
||||||
display.handle_paste(param1)
|
display.handle_paste(param1)
|
||||||
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.6.2"
|
local R_PLC_VERSION = "v1.6.4"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
local rsio = require("scada-common.rsio")
|
|
||||||
|
|
||||||
local config = {}
|
|
||||||
|
|
||||||
-- supervisor comms channel
|
|
||||||
config.SVR_CHANNEL = 16240
|
|
||||||
-- RTU/MODBUS comms channel
|
|
||||||
config.RTU_CHANNEL = 16242
|
|
||||||
-- max trusted modem message distance (0 to disable check)
|
|
||||||
config.TRUSTED_RANGE = 0
|
|
||||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
|
||||||
config.COMMS_TIMEOUT = 5
|
|
||||||
-- facility authentication key (do NOT use one of your passwords)
|
|
||||||
-- this enables verifying that messages are authentic
|
|
||||||
-- all devices on the same network must use the same key
|
|
||||||
-- config.AUTH_KEY = "SCADAfacility123"
|
|
||||||
|
|
||||||
-- alarm sounder volume (0.0 to 3.0, 1.0 being standard max volume, this is the option given to to speaker.play())
|
|
||||||
-- note: alarm sine waves are at half saturation, so that multiple will be required to reach full scale
|
|
||||||
config.SOUNDER_VOLUME = 1.0
|
|
||||||
|
|
||||||
-- log path
|
|
||||||
config.LOG_PATH = "/log.txt"
|
|
||||||
-- log mode
|
|
||||||
-- 0 = APPEND (adds to existing file on start)
|
|
||||||
-- 1 = NEW (replaces existing file on start)
|
|
||||||
config.LOG_MODE = 0
|
|
||||||
-- true to log verbose debug messages
|
|
||||||
config.LOG_DEBUG = false
|
|
||||||
|
|
||||||
-- RTU peripheral devices (named: side/network device name)
|
|
||||||
config.RTU_DEVICES = {
|
|
||||||
{
|
|
||||||
name = "boilerValve_0",
|
|
||||||
index = 1,
|
|
||||||
for_reactor = 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name = "turbineValve_0",
|
|
||||||
index = 1,
|
|
||||||
for_reactor = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-- RTU redstone interface definitions
|
|
||||||
config.RTU_REDSTONE = {
|
|
||||||
-- {
|
|
||||||
-- for_reactor = 1,
|
|
||||||
-- io = {
|
|
||||||
-- {
|
|
||||||
-- port = rsio.IO.WASTE_PO,
|
|
||||||
-- side = "top",
|
|
||||||
-- bundled_color = colors.red
|
|
||||||
-- },
|
|
||||||
-- {
|
|
||||||
-- port = rsio.IO.WASTE_PU,
|
|
||||||
-- side = "top",
|
|
||||||
-- bundled_color = colors.orange
|
|
||||||
-- },
|
|
||||||
-- {
|
|
||||||
-- port = rsio.IO.WASTE_POPL,
|
|
||||||
-- side = "top",
|
|
||||||
-- bundled_color = colors.yellow
|
|
||||||
-- },
|
|
||||||
-- {
|
|
||||||
-- port = rsio.IO.WASTE_AM,
|
|
||||||
-- side = "top",
|
|
||||||
-- bundled_color = colors.lime
|
|
||||||
-- }
|
|
||||||
-- }
|
|
||||||
-- }
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
1505
rtu/configure.lua
Normal file
1505
rtu/configure.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -109,12 +109,10 @@ local function init(panel, units)
|
|||||||
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update)
|
||||||
|
|
||||||
-- unit name identifier (type + index)
|
-- unit name identifier (type + index)
|
||||||
local name = util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", unit.index)
|
local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
||||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=name,height=1}
|
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),height=1}
|
||||||
|
|
||||||
name_box.register(databus.ps, "unit_type_" .. i, function (t)
|
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
||||||
name_box.set_value(util.c(UNIT_TYPE_LABELS[t + 1], " ", unit.index))
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- assignment (unit # or facility)
|
-- assignment (unit # or facility)
|
||||||
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor)
|
||||||
|
63
rtu/rtu.lua
63
rtu/rtu.lua
@ -5,7 +5,6 @@ local log = require("scada-common.log")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local config = require("rtu.config")
|
|
||||||
local databus = require("rtu.databus")
|
local databus = require("rtu.databus")
|
||||||
local modbus = require("rtu.modbus")
|
local modbus = require("rtu.modbus")
|
||||||
|
|
||||||
@ -17,6 +16,51 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
|||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
|
|
||||||
|
---@type rtu_config
|
||||||
|
local config = {}
|
||||||
|
|
||||||
|
rtu.config = config
|
||||||
|
|
||||||
|
-- load the RTU configuration
|
||||||
|
function rtu.load_config()
|
||||||
|
if not settings.load("/rtu.settings") then return false end
|
||||||
|
|
||||||
|
config.Peripherals = settings.get("Peripherals")
|
||||||
|
config.Redstone = settings.get("Redstone")
|
||||||
|
|
||||||
|
config.SpeakerVolume = settings.get("SpeakerVolume")
|
||||||
|
config.SVR_Channel = settings.get("SVR_Channel")
|
||||||
|
config.RTU_Channel = settings.get("RTU_Channel")
|
||||||
|
config.ConnTimeout = settings.get("ConnTimeout")
|
||||||
|
config.TrustedRange = settings.get("TrustedRange")
|
||||||
|
config.AuthKey = settings.get("AuthKey")
|
||||||
|
config.LogMode = settings.get("LogMode")
|
||||||
|
config.LogPath = settings.get("LogPath")
|
||||||
|
config.LogDebug = settings.get("LogDebug")
|
||||||
|
|
||||||
|
local cfv = util.new_validator()
|
||||||
|
|
||||||
|
cfv.assert_type_num(config.SpeakerVolume)
|
||||||
|
cfv.assert_channel(config.SVR_Channel)
|
||||||
|
cfv.assert_channel(config.RTU_Channel)
|
||||||
|
cfv.assert_type_int(config.ConnTimeout)
|
||||||
|
cfv.assert_min(config.ConnTimeout, 2)
|
||||||
|
cfv.assert_type_num(config.TrustedRange)
|
||||||
|
cfv.assert_min(config.TrustedRange, 0)
|
||||||
|
cfv.assert_type_str(config.AuthKey)
|
||||||
|
|
||||||
|
if type(config.AuthKey) == "string" then
|
||||||
|
local len = string.len(config.AuthKey)
|
||||||
|
cfv.assert_eq(len == 0 or len >= 8, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
cfv.assert_type_int(config.LogMode)
|
||||||
|
cfv.assert_type_str(config.LogPath)
|
||||||
|
cfv.assert_type_bool(config.LogDebug)
|
||||||
|
|
||||||
|
return cfv.valid()
|
||||||
|
end
|
||||||
|
|
||||||
-- create a new RTU unit
|
-- create a new RTU unit
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function rtu.init_unit()
|
function rtu.init_unit()
|
||||||
@ -175,7 +219,7 @@ function rtu.init_sounder(speaker)
|
|||||||
function spkr_ctl.continue()
|
function spkr_ctl.continue()
|
||||||
if spkr_ctl.playing then
|
if spkr_ctl.playing then
|
||||||
if spkr_ctl.speaker ~= nil and spkr_ctl.stream.has_next_block() then
|
if spkr_ctl.speaker ~= nil and spkr_ctl.stream.has_next_block() then
|
||||||
local success = spkr_ctl.speaker.playAudio(spkr_ctl.stream.get_next_block(), config.SOUNDER_VOLUME)
|
local success = spkr_ctl.speaker.playAudio(spkr_ctl.stream.get_next_block(), config.SpeakerVolume)
|
||||||
if not success then log.error(util.c("rtu_sounder(", spkr_ctl.name, "): error playing audio")) end
|
if not success then log.error(util.c("rtu_sounder(", spkr_ctl.name, "): error playing audio")) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -203,11 +247,8 @@ end
|
|||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param version string RTU version
|
---@param version string RTU version
|
||||||
---@param nic nic network interface device
|
---@param nic nic network interface device
|
||||||
---@param rtu_channel integer PLC comms channel
|
|
||||||
---@param svr_channel integer supervisor server channel
|
|
||||||
---@param range integer trusted device connection range
|
|
||||||
---@param conn_watchdog watchdog watchdog reference
|
---@param conn_watchdog watchdog watchdog reference
|
||||||
function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
function rtu.comms(version, nic, conn_watchdog)
|
||||||
local self = {
|
local self = {
|
||||||
sv_addr = comms.BROADCAST,
|
sv_addr = comms.BROADCAST,
|
||||||
seq_num = 0,
|
seq_num = 0,
|
||||||
@ -218,13 +259,13 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
|
|
||||||
local insert = table.insert
|
local insert = table.insert
|
||||||
|
|
||||||
comms.set_trusted_range(range)
|
comms.set_trusted_range(config.TrustedRange)
|
||||||
|
|
||||||
-- PRIVATE FUNCTIONS --
|
-- PRIVATE FUNCTIONS --
|
||||||
|
|
||||||
-- configure modem channels
|
-- configure modem channels
|
||||||
nic.closeAll()
|
nic.closeAll()
|
||||||
nic.open(rtu_channel)
|
nic.open(config.RTU_Channel)
|
||||||
|
|
||||||
-- send a scada management packet
|
-- send a scada management packet
|
||||||
---@param msg_type MGMT_TYPE
|
---@param msg_type MGMT_TYPE
|
||||||
@ -236,7 +277,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
m_pkt.make(msg_type, msg)
|
m_pkt.make(msg_type, msg)
|
||||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||||
|
|
||||||
nic.transmit(svr_channel, rtu_channel, s_pkt)
|
nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt)
|
||||||
self.seq_num = self.seq_num + 1
|
self.seq_num = self.seq_num + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -280,7 +321,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
function public.send_modbus(m_pkt)
|
function public.send_modbus(m_pkt)
|
||||||
local s_pkt = comms.scada_packet()
|
local s_pkt = comms.scada_packet()
|
||||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||||
nic.transmit(svr_channel, rtu_channel, s_pkt)
|
nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt)
|
||||||
self.seq_num = self.seq_num + 1
|
self.seq_num = self.seq_num + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -365,7 +406,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
|||||||
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()
|
||||||
|
|
||||||
if l_chan == rtu_channel then
|
if l_chan == config.RTU_Channel then
|
||||||
-- check sequence number
|
-- check sequence number
|
||||||
if self.r_seq_num == nil then
|
if self.r_seq_num == nil then
|
||||||
self.r_seq_num = packet.scada_frame.seq_num()
|
self.r_seq_num = packet.scada_frame.seq_num()
|
||||||
|
267
rtu/startup.lua
267
rtu/startup.lua
@ -15,7 +15,7 @@ local rsio = require("scada-common.rsio")
|
|||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local config = require("rtu.config")
|
local configure = require("rtu.configure")
|
||||||
local databus = require("rtu.databus")
|
local databus = require("rtu.databus")
|
||||||
local modbus = require("rtu.modbus")
|
local modbus = require("rtu.modbus")
|
||||||
local renderer = require("rtu.renderer")
|
local renderer = require("rtu.renderer")
|
||||||
@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
|||||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||||
|
|
||||||
local RTU_VERSION = "v1.6.6"
|
local RTU_VERSION = "v1.7.0"
|
||||||
|
|
||||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||||
@ -40,27 +40,26 @@ local println = util.println
|
|||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- config validation
|
-- get configuration
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
local cfv = util.new_validator()
|
if not rtu.load_config() then
|
||||||
|
-- try to reconfigure (user action)
|
||||||
|
local success, error = configure.configure(true)
|
||||||
|
if success then
|
||||||
|
assert(rtu.load_config(), "failed to load valid RTU configuration")
|
||||||
|
else
|
||||||
|
assert(success, "RTU configuration error: " .. error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
cfv.assert_channel(config.SVR_CHANNEL)
|
local config = rtu.config
|
||||||
cfv.assert_channel(config.RTU_CHANNEL)
|
|
||||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
|
||||||
cfv.assert_type_num(config.COMMS_TIMEOUT)
|
|
||||||
cfv.assert_min(config.COMMS_TIMEOUT, 2)
|
|
||||||
cfv.assert_type_str(config.LOG_PATH)
|
|
||||||
cfv.assert_type_int(config.LOG_MODE)
|
|
||||||
cfv.assert_type_table(config.RTU_DEVICES)
|
|
||||||
cfv.assert_type_table(config.RTU_REDSTONE)
|
|
||||||
assert(cfv.valid(), "bad config file: missing/invalid fields")
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- log init
|
-- log init
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
|
log.init(config.LogPath, config.LogMode, config.LogDebug)
|
||||||
|
|
||||||
log.info("========================================")
|
log.info("========================================")
|
||||||
log.info("BOOTING rtu.startup " .. RTU_VERSION)
|
log.info("BOOTING rtu.startup " .. RTU_VERSION)
|
||||||
@ -85,8 +84,8 @@ local function main()
|
|||||||
ppm.mount_all()
|
ppm.mount_all()
|
||||||
|
|
||||||
-- message authentication init
|
-- message authentication init
|
||||||
if type(config.AUTH_KEY) == "string" then
|
if type(config.AuthKey) == "string" then
|
||||||
network.init_mac(config.AUTH_KEY)
|
network.init_mac(config.AuthKey)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get modem
|
-- get modem
|
||||||
@ -139,126 +138,109 @@ local function main()
|
|||||||
|
|
||||||
local units = __shared_memory.rtu_sys.units
|
local units = __shared_memory.rtu_sys.units
|
||||||
|
|
||||||
local rtu_redstone = config.RTU_REDSTONE
|
local rtu_redstone = config.Redstone
|
||||||
local rtu_devices = config.RTU_DEVICES
|
local rtu_devices = config.Peripherals
|
||||||
|
|
||||||
-- configure RTU gateway based on config file definitions
|
-- configure RTU gateway based on settings file definitions
|
||||||
local function configure()
|
local function sys_config()
|
||||||
-- redstone interfaces
|
-- redstone interfaces
|
||||||
|
local rs_rtus = {}
|
||||||
|
|
||||||
|
-- go through redstone definitions list
|
||||||
for entry_idx = 1, #rtu_redstone do
|
for entry_idx = 1, #rtu_redstone do
|
||||||
local rs_rtu = redstone_rtu.new()
|
local entry = rtu_redstone[entry_idx] ---@type rtu_rs_definition
|
||||||
local io_table = rtu_redstone[entry_idx].io ---@type table
|
local assignment
|
||||||
local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer
|
local for_reactor = entry.unit
|
||||||
|
local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side)
|
||||||
|
|
||||||
-- CHECK: reactor ID must be >= to 1
|
if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then
|
||||||
if (not util.is_int(io_reactor)) or (io_reactor < 0) then
|
---@cast for_reactor integer
|
||||||
local message = util.c("configure> redstone entry #", entry_idx, " : ", io_reactor, " isn't an integer >= 0")
|
assignment = "reactor unit " .. entry.unit
|
||||||
|
if rs_rtus[for_reactor] == nil then
|
||||||
|
log.debug(util.c("sys_config> allocated redstone RTU for reactor unit ", entry.unit))
|
||||||
|
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
||||||
|
end
|
||||||
|
elseif entry.unit == nil then
|
||||||
|
assignment = "facility"
|
||||||
|
for_reactor = 0
|
||||||
|
if rs_rtus[for_reactor] == nil then
|
||||||
|
log.debug(util.c("sys_config> allocated redstone RTU for the facility"))
|
||||||
|
rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} }
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx)
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- CHECK: io table exists
|
|
||||||
if type(io_table) ~= "table" then
|
|
||||||
local message = util.c("configure> redstone entry #", entry_idx, " no IO table found")
|
|
||||||
println(message)
|
|
||||||
log.fatal(message)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local capabilities = {}
|
|
||||||
|
|
||||||
log.debug(util.c("configure> starting redstone RTU I/O linking for reactor ", io_reactor, "..."))
|
|
||||||
|
|
||||||
local continue = true
|
|
||||||
|
|
||||||
-- CHECK: no duplicate entries
|
|
||||||
for i = 1, #units do
|
|
||||||
local unit = units[i] ---@type rtu_unit_registry_entry
|
|
||||||
if unit.reactor == io_reactor and unit.type == RTU_UNIT_TYPE.REDSTONE then
|
|
||||||
-- duplicate entry
|
|
||||||
local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor,
|
|
||||||
" with already defined redstone I/O")
|
|
||||||
println(message)
|
|
||||||
log.warning(message)
|
|
||||||
continue = false
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- not a duplicate
|
|
||||||
if continue then
|
|
||||||
for i = 1, #io_table do
|
|
||||||
local valid = false
|
|
||||||
local conf = io_table[i]
|
|
||||||
|
|
||||||
-- verify configuration
|
-- verify configuration
|
||||||
if rsio.is_valid_port(conf.port) and rsio.is_valid_side(conf.side) then
|
local valid = false
|
||||||
if conf.bundled_color then
|
if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then
|
||||||
valid = rsio.is_color(conf.bundled_color)
|
valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color))
|
||||||
else
|
|
||||||
valid = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local rs_rtu = rs_rtus[for_reactor].rtu
|
||||||
|
local capabilities = rs_rtus[for_reactor].capabilities
|
||||||
|
|
||||||
if not valid then
|
if not valid then
|
||||||
local message = util.c("configure> invalid redstone definition at index ", i, " in definition block #", entry_idx,
|
local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx)
|
||||||
" (for reactor ", io_reactor, ")")
|
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
-- link redstone in RTU
|
-- link redstone in RTU
|
||||||
local mode = rsio.get_io_mode(conf.port)
|
local mode = rsio.get_io_mode(entry.port)
|
||||||
if mode == rsio.IO_MODE.DIGITAL_IN then
|
if mode == rsio.IO_MODE.DIGITAL_IN then
|
||||||
-- can't have duplicate inputs
|
-- can't have duplicate inputs
|
||||||
if util.table_contains(capabilities, conf.port) then
|
if util.table_contains(capabilities, entry.port) then
|
||||||
local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " on side ", conf.side)
|
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_di(conf.side, conf.bundled_color)
|
rs_rtu.link_di(entry.side, entry.color)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
elseif mode == rsio.IO_MODE.DIGITAL_OUT then
|
||||||
rs_rtu.link_do(conf.side, conf.bundled_color)
|
rs_rtu.link_do(entry.side, entry.color)
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
elseif mode == rsio.IO_MODE.ANALOG_IN then
|
||||||
-- can't have duplicate inputs
|
-- can't have duplicate inputs
|
||||||
if util.table_contains(capabilities, conf.port) then
|
if util.table_contains(capabilities, entry.port) then
|
||||||
local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " on side ", conf.side)
|
local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name)
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
else
|
else
|
||||||
rs_rtu.link_ai(conf.side)
|
rs_rtu.link_ai(entry.side)
|
||||||
end
|
end
|
||||||
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
elseif mode == rsio.IO_MODE.ANALOG_OUT then
|
||||||
rs_rtu.link_ao(conf.side)
|
rs_rtu.link_ao(entry.side)
|
||||||
else
|
else
|
||||||
-- should be unreachable code, we already validated ports
|
-- should be unreachable code, we already validated ports
|
||||||
log.error("configure> fell through if chain attempting to identify IO mode", true)
|
log.error("sys_config> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true)
|
||||||
println("configure> encountered a software error, check logs")
|
println("sys_config> encountered a software error, check logs")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(capabilities, conf.port)
|
table.insert(capabilities, entry.port)
|
||||||
|
|
||||||
log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.port),
|
log.debug(util.c("sys_config> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment))
|
||||||
" (", conf.side, ") for reactor ", io_reactor))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- create unit entries for redstone RTUs
|
||||||
|
for for_reactor, def in pairs(rs_rtus) do
|
||||||
---@class rtu_unit_registry_entry
|
---@class rtu_unit_registry_entry
|
||||||
local unit = {
|
local unit = {
|
||||||
uid = 0, ---@type integer
|
uid = 0, ---@type integer
|
||||||
name = "redstone_io", ---@type string
|
name = "redstone_io", ---@type string
|
||||||
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE
|
||||||
index = entry_idx, ---@type integer
|
index = false, ---@type integer|false
|
||||||
reactor = io_reactor, ---@type integer
|
reactor = for_reactor, ---@type integer
|
||||||
device = capabilities, ---@type table use device field for redstone ports
|
device = def.capabilities, ---@type table use device field for redstone ports
|
||||||
is_multiblock = false, ---@type boolean
|
is_multiblock = false, ---@type boolean
|
||||||
formed = nil, ---@type boolean|nil
|
formed = nil, ---@type boolean|nil
|
||||||
hw_state = RTU_UNIT_HW_STATE.OK, ---@type RTU_UNIT_HW_STATE
|
hw_state = RTU_UNIT_HW_STATE.OK, ---@type RTU_UNIT_HW_STATE
|
||||||
rtu = rs_rtu, ---@type rtu_device|rtu_rs_device
|
rtu = def.rtu, ---@type rtu_device|rtu_rs_device
|
||||||
modbus_io = modbus.new(rs_rtu, false),
|
modbus_io = modbus.new(def.rtu, false),
|
||||||
pkt_queue = nil, ---@type mqueue|nil
|
pkt_queue = nil, ---@type mqueue|nil
|
||||||
thread = nil ---@type parallel_thread|nil
|
thread = nil ---@type parallel_thread|nil
|
||||||
}
|
}
|
||||||
@ -266,46 +248,64 @@ local function main()
|
|||||||
table.insert(units, unit)
|
table.insert(units, unit)
|
||||||
|
|
||||||
local for_message = "facility"
|
local for_message = "facility"
|
||||||
if io_reactor > 0 then
|
if util.is_int(for_reactor) then
|
||||||
for_message = util.c("reactor ", io_reactor)
|
for_message = util.c("reactor unit ", for_reactor)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
log.info(util.c("sys_config> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message))
|
||||||
|
|
||||||
unit.uid = #units
|
unit.uid = #units
|
||||||
|
|
||||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
-- mounted peripherals
|
-- mounted peripherals
|
||||||
for i = 1, #rtu_devices do
|
for i = 1, #rtu_devices do
|
||||||
local name = rtu_devices[i].name
|
local entry = rtu_devices[i] ---@type rtu_peri_definition
|
||||||
local index = rtu_devices[i].index
|
local name = entry.name
|
||||||
local for_reactor = rtu_devices[i].for_reactor
|
local index = entry.index
|
||||||
|
local for_reactor = util.trinary(entry.unit == nil, 0, entry.unit)
|
||||||
|
|
||||||
-- CHECK: name is a string
|
-- CHECK: name is a string
|
||||||
if type(name) ~= "string" then
|
if type(name) ~= "string" then
|
||||||
local message = util.c("configure> device entry #", i, ": device ", name, " isn't a string")
|
local message = util.c("sys_config> device entry #", i, ": device ", name, " isn't a string")
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- CHECK: index is an integer >= 1
|
-- CHECK: index type
|
||||||
if (not util.is_int(index)) or (index <= 0) then
|
if (index ~= nil) and (not util.is_int(index)) then
|
||||||
local message = util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1")
|
local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't valid")
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- CHECK: index range
|
||||||
|
local function validate_index(min, max)
|
||||||
|
if (not util.is_int(index)) or ((index < min) and (max ~= nil and index > max)) then
|
||||||
|
local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't >= ", min)
|
||||||
|
if max ~= nil then message = util.c(message, " and <= ", max) end
|
||||||
|
println(message)
|
||||||
|
log.fatal(message)
|
||||||
|
return false
|
||||||
|
else return true end
|
||||||
|
end
|
||||||
|
|
||||||
-- CHECK: reactor is an integer >= 0
|
-- CHECK: reactor is an integer >= 0
|
||||||
if (not util.is_int(for_reactor)) or (for_reactor < 0) then
|
local function validate_assign(for_facility)
|
||||||
local message = util.c("configure> device entry #", i, ": reactor ", for_reactor, " isn't an integer >= 0")
|
if for_facility and for_reactor ~= 0 then
|
||||||
|
local message = util.c("sys_config> device entry #", i, ": must only be for the facility")
|
||||||
println(message)
|
println(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
|
elseif (not for_facility) and ((not util.is_int(for_reactor)) or (for_reactor < 1) or (for_reactor > 4)) then
|
||||||
|
local message = util.c("sys_config> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild")
|
||||||
|
println(message)
|
||||||
|
log.fatal(message)
|
||||||
|
return false
|
||||||
|
else return true end
|
||||||
end
|
end
|
||||||
|
|
||||||
local device = ppm.get_periph(name)
|
local device = ppm.get_periph(name)
|
||||||
@ -318,7 +318,7 @@ local function main()
|
|||||||
local faulted = nil ---@type boolean|nil
|
local faulted = nil ---@type boolean|nil
|
||||||
|
|
||||||
if device == nil then
|
if device == nil then
|
||||||
local message = util.c("configure> '", name, "' not found, using placeholder")
|
local message = util.c("sys_config> '", name, "' not found, using placeholder")
|
||||||
println(message)
|
println(message)
|
||||||
log.warning(message)
|
log.warning(message)
|
||||||
|
|
||||||
@ -330,70 +330,93 @@ local function main()
|
|||||||
|
|
||||||
if type == "boilerValve" then
|
if type == "boilerValve" then
|
||||||
-- boiler multiblock
|
-- boiler multiblock
|
||||||
|
if not validate_index(1, 2) then return false end
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.BOILER_VALVE
|
rtu_type = RTU_UNIT_TYPE.BOILER_VALVE
|
||||||
rtu_iface, faulted = boilerv_rtu.new(device)
|
rtu_iface, faulted = boilerv_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("configure> failed to check if '", name, "' is a formed boiler multiblock"))
|
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
elseif type == "turbineValve" then
|
elseif type == "turbineValve" then
|
||||||
-- turbine multiblock
|
-- turbine multiblock
|
||||||
|
if not validate_index(1, 3) then return false end
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.TURBINE_VALVE
|
rtu_type = RTU_UNIT_TYPE.TURBINE_VALVE
|
||||||
rtu_iface, faulted = turbinev_rtu.new(device)
|
rtu_iface, faulted = turbinev_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("configure> failed to check if '", name, "' is a formed turbine multiblock"))
|
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
elseif type == "dynamicValve" then
|
elseif type == "dynamicValve" then
|
||||||
-- dynamic tank multiblock
|
-- dynamic tank multiblock
|
||||||
|
if entry.unit == nil then
|
||||||
|
if not validate_index(1, 4) then return false end
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
else
|
||||||
|
if not validate_index(1, 1) then return false end
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
rtu_type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||||
rtu_iface, faulted = dynamicv_rtu.new(device)
|
rtu_iface, faulted = dynamicv_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("configure> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
elseif type == "inductionPort" then
|
elseif type == "inductionPort" then
|
||||||
-- induction matrix multiblock
|
-- induction matrix multiblock
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
rtu_type = RTU_UNIT_TYPE.IMATRIX
|
||||||
rtu_iface, faulted = imatrix_rtu.new(device)
|
rtu_iface, faulted = imatrix_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("configure> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
elseif type == "spsPort" then
|
elseif type == "spsPort" then
|
||||||
-- SPS multiblock
|
-- SPS multiblock
|
||||||
|
if not validate_assign(true) then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.SPS
|
rtu_type = RTU_UNIT_TYPE.SPS
|
||||||
rtu_iface, faulted = sps_rtu.new(device)
|
rtu_iface, faulted = sps_rtu.new(device)
|
||||||
is_multiblock = true
|
is_multiblock = true
|
||||||
formed = device.isFormed()
|
formed = device.isFormed()
|
||||||
|
|
||||||
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then
|
||||||
println_ts(util.c("configure> failed to check if '", name, "' is formed"))
|
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||||
log.fatal(util.c("configure> failed to check if '", name, "' is a formed SPS multiblock"))
|
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
elseif type == "solarNeutronActivator" then
|
elseif type == "solarNeutronActivator" then
|
||||||
-- SNA
|
-- SNA
|
||||||
|
if not validate_assign() then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.SNA
|
rtu_type = RTU_UNIT_TYPE.SNA
|
||||||
rtu_iface, faulted = sna_rtu.new(device)
|
rtu_iface, faulted = sna_rtu.new(device)
|
||||||
elseif type == "environmentDetector" then
|
elseif type == "environmentDetector" then
|
||||||
-- advanced peripherals environment detector
|
-- advanced peripherals environment detector
|
||||||
|
if not validate_index(1) then return false end
|
||||||
|
if not validate_assign(entry.unit == nil) then return false end
|
||||||
|
|
||||||
rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR
|
rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR
|
||||||
rtu_iface, faulted = envd_rtu.new(device)
|
rtu_iface, faulted = envd_rtu.new(device)
|
||||||
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
|
elseif type == ppm.VIRTUAL_DEVICE_TYPE then
|
||||||
@ -401,7 +424,7 @@ local function main()
|
|||||||
rtu_type = RTU_UNIT_TYPE.VIRTUAL
|
rtu_type = RTU_UNIT_TYPE.VIRTUAL
|
||||||
rtu_iface = rtu.init_unit().interface()
|
rtu_iface = rtu.init_unit().interface()
|
||||||
else
|
else
|
||||||
local message = util.c("configure> device '", name, "' is not a known type (", type, ")")
|
local message = util.c("sys_config> device '", name, "' is not a known type (", type, ")")
|
||||||
println_ts(message)
|
println_ts(message)
|
||||||
log.fatal(message)
|
log.fatal(message)
|
||||||
return false
|
return false
|
||||||
@ -409,12 +432,12 @@ local function main()
|
|||||||
|
|
||||||
if is_multiblock then
|
if is_multiblock then
|
||||||
if not formed then
|
if not formed then
|
||||||
log.info(util.c("configure> device '", name, "' is not formed"))
|
log.info(util.c("sys_config> device '", name, "' is not formed"))
|
||||||
elseif faulted then
|
elseif faulted then
|
||||||
-- sometimes there is a race condition on server boot where it reports formed, but
|
-- sometimes there is a race condition on server boot where it reports formed, but
|
||||||
-- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later
|
-- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later
|
||||||
formed = false
|
formed = false
|
||||||
log.warning(util.c("configure> device '", name, "' is formed, but initialization had one or more faults: marked as unformed"))
|
log.warning(util.c("sys_config> device '", name, "' is formed, but initialization had one or more faults: marked as unformed"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -423,7 +446,7 @@ local function main()
|
|||||||
uid = 0, ---@type integer
|
uid = 0, ---@type integer
|
||||||
name = name, ---@type string
|
name = name, ---@type string
|
||||||
type = rtu_type, ---@type RTU_UNIT_TYPE
|
type = rtu_type, ---@type RTU_UNIT_TYPE
|
||||||
index = index, ---@type integer
|
index = index or false, ---@type integer|false
|
||||||
reactor = for_reactor, ---@type integer
|
reactor = for_reactor, ---@type integer
|
||||||
device = device, ---@type table
|
device = device, ---@type table
|
||||||
is_multiblock = is_multiblock, ---@type boolean
|
is_multiblock = is_multiblock, ---@type boolean
|
||||||
@ -444,7 +467,7 @@ local function main()
|
|||||||
for_message = util.c("reactor ", for_reactor)
|
for_message = util.c("reactor ", for_reactor)
|
||||||
end
|
end
|
||||||
|
|
||||||
log.info(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message))
|
log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message))
|
||||||
|
|
||||||
rtu_unit.uid = #units
|
rtu_unit.uid = #units
|
||||||
|
|
||||||
@ -465,7 +488,6 @@ local function main()
|
|||||||
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- we made it through all that trusting-user-to-write-a-config-file chaos
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -475,9 +497,9 @@ local function main()
|
|||||||
|
|
||||||
local rtu_state = __shared_memory.rtu_state
|
local rtu_state = __shared_memory.rtu_state
|
||||||
|
|
||||||
log.debug("boot> running configure()")
|
log.debug("boot> running sys_config()")
|
||||||
|
|
||||||
if configure() then
|
if sys_config() then
|
||||||
-- start UI
|
-- start UI
|
||||||
local message
|
local message
|
||||||
rtu_state.fp_ok, message = renderer.try_start_ui(units)
|
rtu_state.fp_ok, message = renderer.try_start_ui(units)
|
||||||
@ -502,12 +524,11 @@ local function main()
|
|||||||
databus.tx_hw_spkr_count(#__shared_memory.rtu_dev.sounders)
|
databus.tx_hw_spkr_count(#__shared_memory.rtu_dev.sounders)
|
||||||
|
|
||||||
-- start connection watchdog
|
-- start connection watchdog
|
||||||
smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT)
|
smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout)
|
||||||
log.debug("startup> conn watchdog started")
|
log.debug("startup> conn watchdog started")
|
||||||
|
|
||||||
-- setup comms
|
-- setup comms
|
||||||
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, config.RTU_CHANNEL, config.SVR_CHANNEL,
|
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog)
|
||||||
config.TRUSTED_RANGE, smem_sys.conn_watchdog)
|
|
||||||
log.debug("startup> comms init")
|
log.debug("startup> comms init")
|
||||||
|
|
||||||
-- init threads
|
-- init threads
|
||||||
|
238
rtu/threads.lua
238
rtu/threads.lua
@ -28,6 +28,147 @@ local UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
|||||||
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
||||||
local COMMS_SLEEP = 100 -- (100ms, 2 ticks)
|
local COMMS_SLEEP = 100 -- (100ms, 2 ticks)
|
||||||
|
|
||||||
|
---@param smem rtu_shared_memory
|
||||||
|
---@param println_ts function
|
||||||
|
---@param iface string
|
||||||
|
---@param type string
|
||||||
|
---@param device table
|
||||||
|
---@param unit rtu_unit_registry_entry
|
||||||
|
local function handle_unit_mount(smem, println_ts, iface, type, device, unit)
|
||||||
|
local sys = smem.rtu_sys
|
||||||
|
|
||||||
|
-- find disconnected device to reconnect
|
||||||
|
-- note: cannot check isFormed as that would yield this coroutine and consume events
|
||||||
|
if unit.name == iface then
|
||||||
|
local resend_advert, faulted, unknown, invalid = false, false, false, false
|
||||||
|
|
||||||
|
local function fail(msg)
|
||||||
|
invalid = true
|
||||||
|
log.error(msg .. " in config")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- found, re-link
|
||||||
|
unit.device = device
|
||||||
|
|
||||||
|
if unit.type == RTU_UNIT_TYPE.VIRTUAL then
|
||||||
|
resend_advert = true
|
||||||
|
if type == "boilerValve" then
|
||||||
|
-- boiler multiblock
|
||||||
|
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("boiler '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
||||||
|
if (unit.index == false) or unit.index < 1 or unit.index > 2 then fail(util.c("boiler '", unit.name, "' cannot init, invalid index provided")) end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.BOILER_VALVE
|
||||||
|
elseif type == "turbineValve" then
|
||||||
|
-- turbine multiblock
|
||||||
|
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("turbine '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
||||||
|
if (unit.index == false) or unit.index < 1 or unit.index > 3 then fail(util.c("turbine '", unit.name, "' cannot init, invalid index provided")) end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.TURBINE_VALVE
|
||||||
|
elseif type == "dynamicValve" then
|
||||||
|
-- dynamic tank multiblock
|
||||||
|
if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("dynamic tank '", unit.name, "' cannot init, no valid assignment provided")) end
|
||||||
|
|
||||||
|
if (unit.reactor == 0 and ((unit.index == false) or unit.index < 1 or unit.index > 4)) or
|
||||||
|
(unit.reactor > 0 and unit.index ~= 1) then
|
||||||
|
fail(util.c("dynamic tank '", unit.name, "' cannot init, invalid index provided"))
|
||||||
|
end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.DYNAMIC_VALVE
|
||||||
|
elseif type == "inductionPort" then
|
||||||
|
-- induction matrix multiblock
|
||||||
|
if unit.reactor ~= 0 then fail(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility")) end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.IMATRIX
|
||||||
|
elseif type == "spsPort" then
|
||||||
|
-- SPS multiblock
|
||||||
|
if unit.reactor ~= 0 then fail(util.c("SPS '", unit.name, "' cannot init, not assigned to facility")) end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.SPS
|
||||||
|
elseif type == "solarNeutronActivator" then
|
||||||
|
-- SNA
|
||||||
|
if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.SNA
|
||||||
|
elseif type == "environmentDetector" then
|
||||||
|
-- advanced peripherals environment detector
|
||||||
|
if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end
|
||||||
|
if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end
|
||||||
|
|
||||||
|
unit.type = RTU_UNIT_TYPE.ENV_DETECTOR
|
||||||
|
else
|
||||||
|
resend_advert = false
|
||||||
|
log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_unit_hw_type(unit.uid, unit.type)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if disconnected on startup, config wouldn't have been validated
|
||||||
|
-- checking now that it has connected; the config isn't valid, so don't connect it
|
||||||
|
if invalid then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.OFFLINE
|
||||||
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- note for multiblock structures: if not formed, indexing the multiblock functions results in a PPM fault
|
||||||
|
|
||||||
|
if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
|
unit.rtu, faulted = boilerv_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
||||||
|
unit.rtu, faulted = turbinev_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
|
unit.rtu, faulted = dynamicv_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.IMATRIX then
|
||||||
|
unit.rtu, faulted = imatrix_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.SPS then
|
||||||
|
unit.rtu, faulted = sps_rtu.new(device)
|
||||||
|
unit.formed = util.trinary(faulted, false, nil)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.SNA then
|
||||||
|
unit.rtu, faulted = sna_rtu.new(device)
|
||||||
|
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
|
unit.rtu, faulted = envd_rtu.new(device)
|
||||||
|
else
|
||||||
|
unknown = true
|
||||||
|
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if unit.is_multiblock then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
||||||
|
if unit.formed == false then
|
||||||
|
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
||||||
|
end
|
||||||
|
elseif faulted then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.FAULTED
|
||||||
|
elseif not unknown then
|
||||||
|
unit.hw_state = UNIT_HW_STATE.OK
|
||||||
|
else
|
||||||
|
unit.hw_state = UNIT_HW_STATE.OFFLINE
|
||||||
|
end
|
||||||
|
|
||||||
|
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
||||||
|
|
||||||
|
if not unknown then
|
||||||
|
unit.modbus_io = modbus.new(unit.rtu, true)
|
||||||
|
|
||||||
|
local type_name = types.rtu_type_to_string(unit.type)
|
||||||
|
local message = util.c("reconnected the ", type_name, " on interface ", unit.name)
|
||||||
|
println_ts(message)
|
||||||
|
log.info(message)
|
||||||
|
|
||||||
|
if resend_advert then
|
||||||
|
sys.rtu_comms.send_advertisement(sys.units)
|
||||||
|
else
|
||||||
|
sys.rtu_comms.send_remounted(unit.uid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- main thread
|
-- main thread
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param smem rtu_shared_memory
|
---@param smem rtu_shared_memory
|
||||||
@ -180,102 +321,7 @@ function threads.thread__main(smem)
|
|||||||
else
|
else
|
||||||
-- relink lost peripheral to correct unit entry
|
-- relink lost peripheral to correct unit entry
|
||||||
for i = 1, #units do
|
for i = 1, #units do
|
||||||
local unit = units[i] ---@type rtu_unit_registry_entry
|
handle_unit_mount(smem, println_ts, param1, type, device, units[i])
|
||||||
|
|
||||||
-- find disconnected device to reconnect
|
|
||||||
-- note: cannot check isFormed as that would yield this coroutine and consume events
|
|
||||||
if unit.name == param1 then
|
|
||||||
local resend_advert = false
|
|
||||||
local faulted = false
|
|
||||||
local unknown = false
|
|
||||||
|
|
||||||
-- found, re-link
|
|
||||||
unit.device = device
|
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.VIRTUAL then
|
|
||||||
resend_advert = true
|
|
||||||
if type == "boilerValve" then
|
|
||||||
-- boiler multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.BOILER_VALVE
|
|
||||||
elseif type == "turbineValve" then
|
|
||||||
-- turbine multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.TURBINE_VALVE
|
|
||||||
elseif type == "inductionPort" then
|
|
||||||
-- induction matrix multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.IMATRIX
|
|
||||||
elseif type == "spsPort" then
|
|
||||||
-- SPS multiblock
|
|
||||||
unit.type = RTU_UNIT_TYPE.SPS
|
|
||||||
elseif type == "solarNeutronActivator" then
|
|
||||||
-- SNA
|
|
||||||
unit.type = RTU_UNIT_TYPE.SNA
|
|
||||||
elseif type == "environmentDetector" then
|
|
||||||
-- advanced peripherals environment detector
|
|
||||||
unit.type = RTU_UNIT_TYPE.ENV_DETECTOR
|
|
||||||
else
|
|
||||||
resend_advert = false
|
|
||||||
log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")"))
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_unit_hw_type(unit.uid, unit.type)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- note for multiblock structures: if not formed, indexing the multiblock functions results in a PPM fault
|
|
||||||
|
|
||||||
if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then
|
|
||||||
unit.rtu, faulted = boilerv_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then
|
|
||||||
unit.rtu, faulted = turbinev_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
|
||||||
unit.rtu, faulted = dynamicv_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.IMATRIX then
|
|
||||||
unit.rtu, faulted = imatrix_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.SPS then
|
|
||||||
unit.rtu, faulted = sps_rtu.new(device)
|
|
||||||
unit.formed = util.trinary(faulted, false, nil)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.SNA then
|
|
||||||
unit.rtu, faulted = sna_rtu.new(device)
|
|
||||||
elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then
|
|
||||||
unit.rtu, faulted = envd_rtu.new(device)
|
|
||||||
else
|
|
||||||
unknown = true
|
|
||||||
log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true)
|
|
||||||
end
|
|
||||||
|
|
||||||
if unit.is_multiblock then
|
|
||||||
unit.hw_state = UNIT_HW_STATE.UNFORMED
|
|
||||||
if unit.formed == false then
|
|
||||||
log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing"))
|
|
||||||
end
|
|
||||||
elseif faulted then
|
|
||||||
unit.hw_state = UNIT_HW_STATE.FAULTED
|
|
||||||
elseif not unknown then
|
|
||||||
unit.hw_state = UNIT_HW_STATE.OK
|
|
||||||
else
|
|
||||||
unit.hw_state = UNIT_HW_STATE.OFFLINE
|
|
||||||
end
|
|
||||||
|
|
||||||
databus.tx_unit_hw_status(unit.uid, unit.hw_state)
|
|
||||||
|
|
||||||
if not unknown then
|
|
||||||
unit.modbus_io = modbus.new(unit.rtu, true)
|
|
||||||
|
|
||||||
local type_name = types.rtu_type_to_string(unit.type)
|
|
||||||
local message = util.c("reconnected the ", type_name, " on interface ", unit.name)
|
|
||||||
println_ts(message)
|
|
||||||
log.info(message)
|
|
||||||
|
|
||||||
if resend_advert then
|
|
||||||
rtu_comms.send_advertisement(units)
|
|
||||||
else
|
|
||||||
rtu_comms.send_remounted(unit.uid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -17,7 +17,7 @@ local max_distance = nil
|
|||||||
local comms = {}
|
local comms = {}
|
||||||
|
|
||||||
-- protocol/data version (protocol/data independent changes tracked by util.lua version)
|
-- protocol/data version (protocol/data independent changes tracked by util.lua version)
|
||||||
comms.version = "2.4.1"
|
comms.version = "2.4.2"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
local PROTOCOL = {
|
local PROTOCOL = {
|
||||||
|
@ -13,6 +13,7 @@ local MODE = { APPEND = 0, NEW = 1 }
|
|||||||
log.MODE = MODE
|
log.MODE = MODE
|
||||||
|
|
||||||
local logger = {
|
local logger = {
|
||||||
|
not_ready = true,
|
||||||
path = "/log.txt",
|
path = "/log.txt",
|
||||||
mode = MODE.APPEND,
|
mode = MODE.APPEND,
|
||||||
debug = false,
|
debug = false,
|
||||||
@ -32,6 +33,8 @@ local free_space = fs.getFreeSpace
|
|||||||
-- private log write function
|
-- private log write function
|
||||||
---@param msg string
|
---@param msg string
|
||||||
local function _log(msg)
|
local function _log(msg)
|
||||||
|
if logger.not_ready then return end
|
||||||
|
|
||||||
local out_of_space = false
|
local out_of_space = false
|
||||||
local time_stamp = os.date("[%c] ")
|
local time_stamp = os.date("[%c] ")
|
||||||
local stamped = time_stamp .. util.strval(msg)
|
local stamped = time_stamp .. util.strval(msg)
|
||||||
@ -94,6 +97,8 @@ function log.init(path, write_mode, include_debug, dmesg_redirect)
|
|||||||
else
|
else
|
||||||
logger.dmesg_out = term.current()
|
logger.dmesg_out = term.current()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
logger.not_ready = false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- close the log file handle
|
-- close the log file handle
|
||||||
|
@ -161,10 +161,10 @@ local function peri_init(iface)
|
|||||||
|
|
||||||
setmetatable(self.device, mt)
|
setmetatable(self.device, mt)
|
||||||
|
|
||||||
return {
|
---@class ppm_entry
|
||||||
type = self.type,
|
local entry = { type = self.type, dev = self.device }
|
||||||
dev = self.device
|
|
||||||
}
|
return entry
|
||||||
end
|
end
|
||||||
|
|
||||||
----------------------
|
----------------------
|
||||||
@ -310,7 +310,11 @@ function ppm.list_avail() return peripheral.getNames() end
|
|||||||
-- list mounted peripherals
|
-- list mounted peripherals
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@return table mounts
|
---@return table mounts
|
||||||
function ppm.list_mounts() return ppm_sys.mounts end
|
function ppm.list_mounts()
|
||||||
|
local list = {}
|
||||||
|
for k, v in pairs(ppm_sys.mounts) do list[k] = v end
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
-- get a mounted peripheral side/interface by device table
|
-- get a mounted peripheral side/interface by device table
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
|
@ -82,16 +82,23 @@ rsio.IO_LVL = IO_LVL
|
|||||||
rsio.IO_DIR = IO_DIR
|
rsio.IO_DIR = IO_DIR
|
||||||
rsio.IO_MODE = IO_MODE
|
rsio.IO_MODE = IO_MODE
|
||||||
rsio.IO = IO_PORT
|
rsio.IO = IO_PORT
|
||||||
|
rsio.NUM_PORTS = IO_PORT.U_EMER_COOL
|
||||||
|
|
||||||
|
-- self checks
|
||||||
|
|
||||||
|
local dup_chk = {}
|
||||||
|
for _, v in pairs(IO_PORT) do
|
||||||
|
assert(dup_chk[v] ~= true, "duplicate in port list")
|
||||||
|
dup_chk[v] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(#dup_chk == rsio.NUM_PORTS, "port list malformed")
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Utility Functions
|
--#region Utility Functions
|
||||||
|
|
||||||
-- port to string
|
local PORT_NAMES = {
|
||||||
---@nodiscard
|
|
||||||
---@param port IO_PORT
|
|
||||||
function rsio.to_string(port)
|
|
||||||
local names = {
|
|
||||||
"F_SCRAM",
|
"F_SCRAM",
|
||||||
"F_ACK",
|
"F_ACK",
|
||||||
"R_SCRAM",
|
"R_SCRAM",
|
||||||
@ -120,8 +127,14 @@ function rsio.to_string(port)
|
|||||||
"U_EMER_COOL"
|
"U_EMER_COOL"
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.is_int(port) and port > 0 and port <= #names then
|
assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect")
|
||||||
return names[port]
|
|
||||||
|
-- port to string
|
||||||
|
---@nodiscard
|
||||||
|
---@param port IO_PORT
|
||||||
|
function rsio.to_string(port)
|
||||||
|
if util.is_int(port) and port > 0 and port <= #PORT_NAMES then
|
||||||
|
return PORT_NAMES[port]
|
||||||
else
|
else
|
||||||
return "UNKNOWN"
|
return "UNKNOWN"
|
||||||
end
|
end
|
||||||
@ -266,12 +279,24 @@ end
|
|||||||
|
|
||||||
-- check if a color is a valid single color
|
-- check if a color is a valid single color
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
---@param color integer
|
---@param color any
|
||||||
---@return boolean valid
|
---@return boolean valid
|
||||||
function rsio.is_color(color)
|
function rsio.is_color(color)
|
||||||
return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0)
|
return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- color to string
|
||||||
|
---@nodiscard
|
||||||
|
---@param color color
|
||||||
|
---@return string
|
||||||
|
function rsio.color_name(color)
|
||||||
|
local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" }
|
||||||
|
|
||||||
|
if rsio.is_color(color) then
|
||||||
|
return color_name_map[color]
|
||||||
|
else return "unknown" end
|
||||||
|
end
|
||||||
|
|
||||||
--#endregion
|
--#endregion
|
||||||
|
|
||||||
--#region Digital I/O
|
--#region Digital I/O
|
||||||
|
@ -63,7 +63,7 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end
|
|||||||
|
|
||||||
---@class rtu_advertisement
|
---@class rtu_advertisement
|
||||||
---@field type RTU_UNIT_TYPE
|
---@field type RTU_UNIT_TYPE
|
||||||
---@field index integer
|
---@field index integer|false
|
||||||
---@field reactor integer
|
---@field reactor integer
|
||||||
---@field rsio table|nil
|
---@field rsio table|nil
|
||||||
|
|
||||||
@ -252,6 +252,14 @@ types.ALARM_STATE_NAMES = {
|
|||||||
-- STRING TYPES --
|
-- STRING TYPES --
|
||||||
--#region
|
--#region
|
||||||
|
|
||||||
|
---@alias side
|
||||||
|
---|"top"
|
||||||
|
---|"bottom"
|
||||||
|
---|"left"
|
||||||
|
---|"right"
|
||||||
|
---|"front"
|
||||||
|
---|"back"
|
||||||
|
|
||||||
---@alias os_event
|
---@alias os_event
|
||||||
---| "alarm"
|
---| "alarm"
|
||||||
---| "char"
|
---| "char"
|
||||||
|
@ -14,11 +14,15 @@ local print = print
|
|||||||
local tostring = tostring
|
local tostring = tostring
|
||||||
local type = type
|
local type = type
|
||||||
|
|
||||||
|
local t_concat = table.concat
|
||||||
|
local t_insert = table.insert
|
||||||
|
local t_unpack = table.unpack
|
||||||
|
|
||||||
---@class util
|
---@class util
|
||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
-- scada-common version
|
-- scada-common version
|
||||||
util.version = "1.1.5"
|
util.version = "1.1.9"
|
||||||
|
|
||||||
util.TICK_TIME_S = 0.05
|
util.TICK_TIME_S = 0.05
|
||||||
util.TICK_TIME_MS = 50
|
util.TICK_TIME_MS = 50
|
||||||
@ -70,7 +74,7 @@ function util.strval(val)
|
|||||||
if t == "string" then return val end
|
if t == "string" then return val end
|
||||||
-- this depends on Lua short-circuiting the or check for metatables (note: metatables won't have metatables)
|
-- this depends on Lua short-circuiting the or check for metatables (note: metatables won't have metatables)
|
||||||
if (t == "table" and (getmetatable(val) == nil or getmetatable(val).__tostring == nil)) or t == "function" then
|
if (t == "table" and (getmetatable(val) == nil or getmetatable(val).__tostring == nil)) or t == "function" then
|
||||||
return table.concat{"[", tostring(val), "]"}
|
return t_concat{"[", tostring(val), "]"}
|
||||||
else return tostring(val) end
|
else return tostring(val) end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -90,7 +94,7 @@ function util.pad(str, n)
|
|||||||
local lpad = math.floor((n - len) / 2)
|
local lpad = math.floor((n - len) / 2)
|
||||||
local rpad = (n - len) - lpad
|
local rpad = (n - len) - lpad
|
||||||
|
|
||||||
return table.concat{util.spaces(lpad), str, util.spaces(rpad)}
|
return t_concat{util.spaces(lpad), str, util.spaces(rpad)}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- wrap a string into a table of lines
|
-- wrap a string into a table of lines
|
||||||
@ -109,8 +113,9 @@ function util.strwrap(str, limit) return cc_strings.wrap(str, limit) end
|
|||||||
---@diagnostic disable-next-line: unused-vararg
|
---@diagnostic disable-next-line: unused-vararg
|
||||||
function util.concat(...)
|
function util.concat(...)
|
||||||
local strings = {}
|
local strings = {}
|
||||||
for i = 1, #arg do strings[i] = util.strval(arg[i]) end
|
---@diagnostic disable-next-line: undefined-field
|
||||||
return table.concat(strings)
|
for i = 1, arg.n do strings[i] = util.strval(arg[i]) end
|
||||||
|
return t_concat(strings)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- alias
|
-- alias
|
||||||
@ -121,7 +126,7 @@ util.c = util.concat
|
|||||||
---@param format string
|
---@param format string
|
||||||
---@vararg any
|
---@vararg any
|
||||||
---@diagnostic disable-next-line: unused-vararg
|
---@diagnostic disable-next-line: unused-vararg
|
||||||
function util.sprintf(format, ...) return string.format(format, table.unpack(arg)) end
|
function util.sprintf(format, ...) return string.format(format, t_unpack(arg)) end
|
||||||
|
|
||||||
-- luacheck: unused args
|
-- luacheck: unused args
|
||||||
|
|
||||||
@ -185,7 +190,7 @@ function util.mov_avg(length, default)
|
|||||||
---@param x number value
|
---@param x number value
|
||||||
function public.reset(x)
|
function public.reset(x)
|
||||||
data = {}
|
data = {}
|
||||||
for _ = 1, length do table.insert(data, x) end
|
for _ = 1, length do t_insert(data, x) end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- record a new value
|
-- record a new value
|
||||||
@ -482,6 +487,7 @@ function util.new_validator()
|
|||||||
function public.assert_type_str(value) valid = valid and type(value) == "string" end
|
function public.assert_type_str(value) valid = valid and type(value) == "string" end
|
||||||
function public.assert_type_table(value) valid = valid and type(value) == "table" end
|
function public.assert_type_table(value) valid = valid and type(value) == "table" end
|
||||||
|
|
||||||
|
function public.assert(check) valid = valid and (check == true) end
|
||||||
function public.assert_eq(check, expect) valid = valid and check == expect end
|
function public.assert_eq(check, expect) valid = valid and check == expect end
|
||||||
function public.assert_min(check, min) valid = valid and check >= min end
|
function public.assert_min(check, min) valid = valid and check >= min end
|
||||||
function public.assert_min_ex(check, min) valid = valid and check > min end
|
function public.assert_min_ex(check, min) valid = valid and check > min end
|
||||||
|
22
startup.lua
22
startup.lua
@ -1,30 +1,28 @@
|
|||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local BOOTLOADER_VERSION = "0.3"
|
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
|
||||||
|
local BOOTLOADER_VERSION = "0.4"
|
||||||
|
|
||||||
println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION)
|
println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION)
|
||||||
|
println("BOOT> SCANNING FOR APPLICATIONS...")
|
||||||
|
|
||||||
local exit_code ---@type boolean
|
local exit_code
|
||||||
|
|
||||||
println_ts("BOOT> SCANNING FOR APPLICATIONS...")
|
|
||||||
|
|
||||||
if fs.exists("reactor-plc/startup.lua") then
|
if fs.exists("reactor-plc/startup.lua") then
|
||||||
println("BOOT> FOUND REACTOR PLC CODE: EXEC STARTUP")
|
println("BOOT> EXEC REACTOR PLC STARTUP")
|
||||||
exit_code = shell.execute("reactor-plc/startup")
|
exit_code = shell.execute("reactor-plc/startup")
|
||||||
elseif fs.exists("rtu/startup.lua") then
|
elseif fs.exists("rtu/startup.lua") then
|
||||||
println("BOOT> FOUND RTU CODE: EXEC STARTUP")
|
println("BOOT> EXEC RTU STARTUP")
|
||||||
exit_code = shell.execute("rtu/startup")
|
exit_code = shell.execute("rtu/startup")
|
||||||
elseif fs.exists("supervisor/startup.lua") then
|
elseif fs.exists("supervisor/startup.lua") then
|
||||||
println("BOOT> FOUND SUPERVISOR CODE: EXEC STARTUP")
|
println("BOOT> EXEC SUPERVISOR STARTUP")
|
||||||
exit_code = shell.execute("supervisor/startup")
|
exit_code = shell.execute("supervisor/startup")
|
||||||
elseif fs.exists("coordinator/startup.lua") then
|
elseif fs.exists("coordinator/startup.lua") then
|
||||||
println("BOOT> FOUND COORDINATOR CODE: EXEC STARTUP")
|
println("BOOT> EXEC COORDINATOR STARTUP")
|
||||||
exit_code = shell.execute("coordinator/startup")
|
exit_code = shell.execute("coordinator/startup")
|
||||||
elseif fs.exists("pocket/startup.lua") then
|
elseif fs.exists("pocket/startup.lua") then
|
||||||
println("BOOT> FOUND POCKET CODE: EXEC STARTUP")
|
println("BOOT> EXEC POCKET STARTUP")
|
||||||
exit_code = shell.execute("pocket/startup")
|
exit_code = shell.execute("pocket/startup")
|
||||||
else
|
else
|
||||||
println("BOOT> NO SCADA STARTUP FOUND")
|
println("BOOT> NO SCADA STARTUP FOUND")
|
||||||
@ -32,6 +30,6 @@ else
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if not exit_code then println_ts("BOOT> APPLICATION CRASHED") end
|
if not exit_code then println("BOOT> APPLICATION CRASHED") end
|
||||||
|
|
||||||
return exit_code
|
return exit_code
|
||||||
|
@ -232,9 +232,7 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
|
|
||||||
-- link a redstone RTU session
|
-- link a redstone RTU session
|
||||||
---@param rs_unit unit_session
|
---@param rs_unit unit_session
|
||||||
function public.add_redstone(rs_unit)
|
function public.add_redstone(rs_unit) table.insert(self.redstone, rs_unit) end
|
||||||
table.insert(self.redstone, rs_unit)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- link an induction matrix RTU session
|
-- link an induction matrix RTU session
|
||||||
---@param imatrix unit_session
|
---@param imatrix unit_session
|
||||||
@ -258,23 +256,11 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
|
|
||||||
-- link a dynamic tank RTU session
|
-- link a dynamic tank RTU session
|
||||||
---@param dynamic_tank unit_session
|
---@param dynamic_tank unit_session
|
||||||
---@return boolean linked dynamic tank accepted (max 1)
|
function public.add_tank(dynamic_tank) table.insert(self.tanks, dynamic_tank) end
|
||||||
function public.add_tank(dynamic_tank)
|
|
||||||
if #self.tanks == 0 then
|
|
||||||
table.insert(self.tanks, dynamic_tank)
|
|
||||||
return true
|
|
||||||
else return false end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- link an environment detector RTU session
|
-- link an environment detector RTU session
|
||||||
---@param envd unit_session
|
---@param envd unit_session
|
||||||
---@return boolean linked environment detector accepted (max 1)
|
function public.add_envd(envd) table.insert(self.envd, envd) end
|
||||||
function public.add_envd(envd)
|
|
||||||
if #self.envd == 0 then
|
|
||||||
table.insert(self.envd, envd)
|
|
||||||
return true
|
|
||||||
else return false end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- purge devices associated with the given RTU session ID
|
-- purge devices associated with the given RTU session ID
|
||||||
---@param session integer RTU session ID
|
---@param session integer RTU session ID
|
||||||
@ -643,11 +629,16 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- check for facility radiation
|
-- check for facility radiation
|
||||||
if self.envd[1] ~= nil then
|
if #self.envd > 0 then
|
||||||
local envd = self.envd[1] ---@type unit_session
|
local max_rad = 0
|
||||||
local e_db = envd.get_db() ---@type envd_session_db
|
|
||||||
|
|
||||||
astatus.radiation = e_db.radiation_raw > ALARM_LIMS.FAC_HIGH_RAD
|
for i = 1, #self.envd do
|
||||||
|
local envd = self.envd[i] ---@type unit_session
|
||||||
|
local e_db = envd.get_db() ---@type envd_session_db
|
||||||
|
if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end
|
||||||
|
end
|
||||||
|
|
||||||
|
astatus.radiation = max_rad >= ALARM_LIMS.FAC_HIGH_RAD
|
||||||
else
|
else
|
||||||
-- don't clear, if it is true then we lost it with high radiation, so just keep alarming
|
-- don't clear, if it is true then we lost it with high radiation, so just keep alarming
|
||||||
-- operator can restart the system or hit the stop/reset button
|
-- operator can restart the system or hit the stop/reset button
|
||||||
@ -1093,7 +1084,7 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
build.induction = {}
|
build.induction = {}
|
||||||
for i = 1, #self.induction do
|
for i = 1, #self.induction do
|
||||||
local matrix = self.induction[i] ---@type unit_session
|
local matrix = self.induction[i] ---@type unit_session
|
||||||
build.induction[matrix.get_device_idx()] = { matrix.get_db().formed, matrix.get_db().build }
|
build.induction[i] = { matrix.get_db().formed, matrix.get_db().build }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1101,7 +1092,7 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
build.sps = {}
|
build.sps = {}
|
||||||
for i = 1, #self.sps do
|
for i = 1, #self.sps do
|
||||||
local sps = self.sps[i] ---@type unit_session
|
local sps = self.sps[i] ---@type unit_session
|
||||||
build.sps[sps.get_device_idx()] = { sps.get_db().formed, sps.get_db().build }
|
build.sps[i] = { sps.get_db().formed, sps.get_db().build }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1160,7 +1151,7 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
for i = 1, #self.induction do
|
for i = 1, #self.induction do
|
||||||
local matrix = self.induction[i] ---@type unit_session
|
local matrix = self.induction[i] ---@type unit_session
|
||||||
local db = matrix.get_db() ---@type imatrix_session_db
|
local db = matrix.get_db() ---@type imatrix_session_db
|
||||||
status.induction[matrix.get_device_idx()] = { matrix.is_faulted(), db.formed, db.state, db.tanks }
|
status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- status of sps
|
-- status of sps
|
||||||
@ -1168,7 +1159,7 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
for i = 1, #self.sps do
|
for i = 1, #self.sps do
|
||||||
local sps = self.sps[i] ---@type unit_session
|
local sps = self.sps[i] ---@type unit_session
|
||||||
local db = sps.get_db() ---@type sps_session_db
|
local db = sps.get_db() ---@type sps_session_db
|
||||||
status.sps[sps.get_device_idx()] = { sps.is_faulted(), db.formed, db.state, db.tanks }
|
status.sps[i] = { sps.is_faulted(), db.formed, db.state, db.tanks }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- status of dynamic tanks
|
-- status of dynamic tanks
|
||||||
@ -1180,10 +1171,11 @@ function facility.new(num_reactors, cooling_conf)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- radiation monitors (environment detectors)
|
-- radiation monitors (environment detectors)
|
||||||
status.rad_mon = {}
|
status.envds = {}
|
||||||
for i = 1, #self.envd do
|
for i = 1, #self.envd do
|
||||||
local envd = self.envd[i] ---@type unit_session
|
local envd = self.envd[i] ---@type unit_session
|
||||||
status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), envd.get_db().radiation }
|
local db = envd.get_db() ---@type envd_session_db
|
||||||
|
status.envds[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw }
|
||||||
end
|
end
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
@ -100,7 +100,7 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement
|
|||||||
-- validate unit advertisement
|
-- validate unit advertisement
|
||||||
|
|
||||||
local advert_validator = util.new_validator()
|
local advert_validator = util.new_validator()
|
||||||
advert_validator.assert_type_int(unit_advert.index)
|
advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false))
|
||||||
advert_validator.assert_type_int(unit_advert.reactor)
|
advert_validator.assert_type_int(unit_advert.reactor)
|
||||||
|
|
||||||
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
if u_type == RTU_UNIT_TYPE.REDSTONE then
|
||||||
@ -108,7 +108,7 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement
|
|||||||
end
|
end
|
||||||
|
|
||||||
if advert_validator.valid() then
|
if advert_validator.valid() then
|
||||||
advert_validator.assert_min(unit_advert.index, 1)
|
if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end
|
||||||
advert_validator.assert_min(unit_advert.reactor, 0)
|
advert_validator.assert_min(unit_advert.reactor, 0)
|
||||||
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
advert_validator.assert_max(unit_advert.reactor, #self.fac_units)
|
||||||
if not advert_validator.valid() then u_type = false end
|
if not advert_validator.valid() then u_type = false end
|
||||||
|
@ -37,13 +37,16 @@ local PERIODICS = {
|
|||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU unit message out queue
|
||||||
function boilerv.new(session_id, unit_id, advert, out_queue)
|
function boilerv.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.BOILER_VALVE then
|
if advert.type ~= RTU_UNIT_TYPE.BOILER_VALVE then
|
||||||
log.error("attempt to instantiate boilerv RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate boilerv RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
|
return nil
|
||||||
|
elseif not util.is_int(advert.index) then
|
||||||
|
log.error("attempt to instantiate boilerv RTU without index")
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").boilerv(" .. advert.index .. "): "
|
local log_tag = util.c("session.rtu(", session_id, ").boilerv(", advert.index, ")[@", unit_id, "]: ")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -49,13 +49,16 @@ local PERIODICS = {
|
|||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU unit message out queue
|
||||||
function dynamicv.new(session_id, unit_id, advert, out_queue)
|
function dynamicv.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
if advert.type ~= RTU_UNIT_TYPE.DYNAMIC_VALVE then
|
||||||
log.error("attempt to instantiate dynamicv RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate dynamicv RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
|
return nil
|
||||||
|
elseif not util.is_int(advert.index) then
|
||||||
|
log.error("attempt to instantiate dynamicv RTU without index")
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").dynamicv(" .. advert.index .. "): "
|
local log_tag = util.c("session.rtu(", session_id, ").dynamicv(", advert.index, ")[@", unit_id, "]: ")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -28,13 +28,16 @@ local PERIODICS = {
|
|||||||
---@param advert rtu_advertisement
|
---@param advert rtu_advertisement
|
||||||
---@param out_queue mqueue
|
---@param out_queue mqueue
|
||||||
function envd.new(session_id, unit_id, advert, out_queue)
|
function envd.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.ENV_DETECTOR then
|
if advert.type ~= RTU_UNIT_TYPE.ENV_DETECTOR then
|
||||||
log.error("attempt to instantiate envd RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate envd RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
|
return nil
|
||||||
|
elseif not util.is_int(advert.index) then
|
||||||
|
log.error("attempt to instantiate envd RTU without index")
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").envd(" .. advert.index .. "): "
|
local log_tag = util.c("session.rtu(", session_id, ").envd(", advert.index, ")[@", unit_id, "]: ")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -37,13 +37,13 @@ local PERIODICS = {
|
|||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU unit message out queue
|
||||||
function imatrix.new(session_id, unit_id, advert, out_queue)
|
function imatrix.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.IMATRIX then
|
if advert.type ~= RTU_UNIT_TYPE.IMATRIX then
|
||||||
log.error("attempt to instantiate imatrix RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate imatrix RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").imatrix(" .. advert.index .. "): "
|
local log_tag = util.c("session.rtu(", session_id, ").imatrix[@", unit_id, "]: ")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -52,12 +52,11 @@ local PERIODICS = {
|
|||||||
function redstone.new(session_id, unit_id, advert, out_queue)
|
function redstone.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- type check
|
||||||
if advert.type ~= RTU_UNIT_TYPE.REDSTONE then
|
if advert.type ~= RTU_UNIT_TYPE.REDSTONE then
|
||||||
log.error("attempt to instantiate redstone RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate redstone RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- for redstone, use unit ID not device index
|
local log_tag = util.c("session.rtu(", session_id, ").redstone[@", unit_id, "]: ")
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").redstone(" .. unit_id .. "): "
|
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -36,11 +36,11 @@ local PERIODICS = {
|
|||||||
function sna.new(session_id, unit_id, advert, out_queue)
|
function sna.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- type check
|
||||||
if advert.type ~= RTU_UNIT_TYPE.SNA then
|
if advert.type ~= RTU_UNIT_TYPE.SNA then
|
||||||
log.error("attempt to instantiate sna RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate sna RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").sna(" .. advert.index .. "): "
|
local log_tag = util.c("session.rtu(", session_id, ").sna[@", unit_id, "]: ")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -39,11 +39,11 @@ local PERIODICS = {
|
|||||||
function sps.new(session_id, unit_id, advert, out_queue)
|
function sps.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- type check
|
||||||
if advert.type ~= RTU_UNIT_TYPE.SPS then
|
if advert.type ~= RTU_UNIT_TYPE.SPS then
|
||||||
log.error("attempt to instantiate sps RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate sps RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").sps(" .. advert.index .. "): "
|
local log_tag = util.c("session.rtu(", session_id, ").sps[@", unit_id, "]: ")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -49,13 +49,16 @@ local PERIODICS = {
|
|||||||
---@param advert rtu_advertisement RTU advertisement table
|
---@param advert rtu_advertisement RTU advertisement table
|
||||||
---@param out_queue mqueue RTU unit message out queue
|
---@param out_queue mqueue RTU unit message out queue
|
||||||
function turbinev.new(session_id, unit_id, advert, out_queue)
|
function turbinev.new(session_id, unit_id, advert, out_queue)
|
||||||
-- type check
|
-- checks
|
||||||
if advert.type ~= RTU_UNIT_TYPE.TURBINE_VALVE then
|
if advert.type ~= RTU_UNIT_TYPE.TURBINE_VALVE then
|
||||||
log.error("attempt to instantiate turbinev RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.")
|
log.error("attempt to instantiate turbinev RTU for type " .. types.rtu_type_to_string(advert.type))
|
||||||
|
return nil
|
||||||
|
elseif not util.is_int(advert.index) then
|
||||||
|
log.error("attempt to instantiate turbinev RTU without index")
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local log_tag = "session.rtu(" .. session_id .. ").turbinev(" .. advert.index .. "): "
|
local log_tag = util.c("session.rtu(", session_id, ").turbinev(", advert.index, ")[@", unit_id, "]: ")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS),
|
||||||
|
@ -152,7 +152,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
|
|||||||
function public.get_unit_id() return unit_id end
|
function public.get_unit_id() return unit_id end
|
||||||
-- get the device index
|
-- get the device index
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_device_idx() return self.device_index end
|
function public.get_device_idx() return self.device_index or 0 end
|
||||||
-- get the reactor ID
|
-- get the reactor ID
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
function public.get_reactor() return self.reactor end
|
function public.get_reactor() return self.reactor end
|
||||||
|
@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor")
|
|||||||
|
|
||||||
local svsessions = require("supervisor.session.svsessions")
|
local svsessions = require("supervisor.session.svsessions")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "v1.0.9"
|
local SUPERVISOR_VERSION = "v1.1.0"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
|
@ -864,10 +864,11 @@ function unit.new(reactor_id, num_boilers, num_turbines)
|
|||||||
status.sna = { #self.snas, public.get_sna_rate(), total_peak }
|
status.sna = { #self.snas, public.get_sna_rate(), total_peak }
|
||||||
|
|
||||||
-- radiation monitors (environment detectors)
|
-- radiation monitors (environment detectors)
|
||||||
status.rad_mon = {}
|
status.envds = {}
|
||||||
for i = 1, #self.envd do
|
for i = 1, #self.envd do
|
||||||
local envd = self.envd[i] ---@type unit_session
|
local envd = self.envd[i] ---@type unit_session
|
||||||
status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), envd.get_db().radiation }
|
local db = envd.get_db() ---@type envd_session_db
|
||||||
|
status.envds[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw }
|
||||||
end
|
end
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
@ -47,8 +47,9 @@ function logic.update_annunciator(self)
|
|||||||
|
|
||||||
local num_boilers = self.num_boilers
|
local num_boilers = self.num_boilers
|
||||||
local num_turbines = self.num_turbines
|
local num_turbines = self.num_turbines
|
||||||
|
local annunc = self.db.annunciator
|
||||||
|
|
||||||
self.db.annunciator.RCSFault = false
|
annunc.RCSFault = false
|
||||||
|
|
||||||
-- variables for boiler, or reactor if no boilers used
|
-- variables for boiler, or reactor if no boilers used
|
||||||
local total_boil_rate = 0.0
|
local total_boil_rate = 0.0
|
||||||
@ -57,14 +58,14 @@ function logic.update_annunciator(self)
|
|||||||
-- REACTOR --
|
-- REACTOR --
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
self.db.annunciator.AutoControl = self.auto_engaged
|
annunc.AutoControl = self.auto_engaged
|
||||||
|
|
||||||
-- check PLC status
|
-- check PLC status
|
||||||
self.db.annunciator.PLCOnline = self.plc_i ~= nil
|
annunc.PLCOnline = self.plc_i ~= nil
|
||||||
|
|
||||||
local plc_ready = self.db.annunciator.PLCOnline
|
local plc_ready = annunc.PLCOnline
|
||||||
|
|
||||||
if self.db.annunciator.PLCOnline then
|
if plc_ready then
|
||||||
local plc_db = self.plc_i.get_db()
|
local plc_db = self.plc_i.get_db()
|
||||||
|
|
||||||
-- update ready state
|
-- update ready state
|
||||||
@ -110,29 +111,29 @@ function logic.update_annunciator(self)
|
|||||||
|
|
||||||
-- heartbeat blink about every second
|
-- heartbeat blink about every second
|
||||||
if self.last_heartbeat + 1000 < plc_db.last_status_update then
|
if self.last_heartbeat + 1000 < plc_db.last_status_update then
|
||||||
self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat
|
annunc.PLCHeartbeat = not annunc.PLCHeartbeat
|
||||||
self.last_heartbeat = plc_db.last_status_update
|
self.last_heartbeat = plc_db.last_status_update
|
||||||
end
|
end
|
||||||
|
|
||||||
local flow_low = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, ANNUNC_LIMS.RCSFlowLow_NA, ANNUNC_LIMS.RCSFlowLow_H2O)
|
local flow_low = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, ANNUNC_LIMS.RCSFlowLow_NA, ANNUNC_LIMS.RCSFlowLow_H2O)
|
||||||
|
|
||||||
-- update other annunciator fields
|
-- update other annunciator fields
|
||||||
self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped
|
annunc.ReactorSCRAM = plc_db.rps_tripped
|
||||||
self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL
|
annunc.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL
|
||||||
self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC
|
annunc.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC
|
||||||
self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool)
|
annunc.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool)
|
||||||
self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low
|
annunc.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low
|
||||||
self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow
|
annunc.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow
|
||||||
self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh
|
annunc.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh
|
||||||
self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT
|
annunc.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT
|
||||||
self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow
|
annunc.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow
|
||||||
self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh
|
annunc.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh
|
||||||
|
|
||||||
local heating_rate_conv = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, 200000, 20000)
|
local heating_rate_conv = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, 200000, 20000)
|
||||||
local high_rate = plc_db.mek_status.burn_rate >= (plc_db.mek_status.ccool_amnt * 0.27 / heating_rate_conv)
|
local high_rate = plc_db.mek_status.burn_rate >= (plc_db.mek_status.ccool_amnt * 0.27 / heating_rate_conv)
|
||||||
-- this advisory applies when no coolant is buffered (which we can't easily determine)<br>
|
-- this advisory applies when no coolant is buffered (which we can't easily determine)<br>
|
||||||
-- it's a rough estimation, see GitHub cc-mek-scada/wiki/High-Rate-Calculation
|
-- it's a rough estimation, see GitHub cc-mek-scada/wiki/High-Rate-Calculation
|
||||||
self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and high_rate
|
annunc.HighStartupRate = not plc_db.mek_status.status and high_rate
|
||||||
|
|
||||||
-- if no boilers, use reactor heating rate to check for boil rate mismatch
|
-- if no boilers, use reactor heating rate to check for boil rate mismatch
|
||||||
if num_boilers == 0 then
|
if num_boilers == 0 then
|
||||||
@ -146,21 +147,25 @@ function logic.update_annunciator(self)
|
|||||||
-- MISC RTUs --
|
-- MISC RTUs --
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
self.db.annunciator.RadiationMonitor = 1
|
local max_rad, any_faulted = 0, false
|
||||||
self.db.annunciator.RadiationWarning = false
|
|
||||||
for i = 1, #self.envd do
|
for i = 1, #self.envd do
|
||||||
local envd = self.envd[i] ---@type unit_session
|
local envd = self.envd[i] ---@type unit_session
|
||||||
self.db.annunciator.RadiationMonitor = util.trinary(envd.is_faulted(), 2, 3)
|
local db = envd.get_db() ---@type envd_session_db
|
||||||
self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw >= ANNUNC_LIMS.RadiationWarning
|
any_faulted = any_faulted or envd.is_faulted()
|
||||||
break
|
if db.radiation_raw > max_rad then max_rad = db.radiation_raw end
|
||||||
end
|
end
|
||||||
|
|
||||||
self.db.annunciator.EmergencyCoolant = 1
|
annunc.RadiationMonitor = util.trinary(#self.envd == 0, 1, util.trinary(any_faulted, 2, 3))
|
||||||
|
annunc.RadiationWarning = max_rad >= ANNUNC_LIMS.RadiationWarning
|
||||||
|
|
||||||
|
annunc.EmergencyCoolant = 1
|
||||||
|
|
||||||
for i = 1, #self.redstone do
|
for i = 1, #self.redstone do
|
||||||
local db = self.redstone[i].get_db() ---@type redstone_session_db
|
local db = self.redstone[i].get_db() ---@type redstone_session_db
|
||||||
local io = db.io[IO.U_EMER_COOL] ---@type rs_db_dig_io|nil
|
local io = db.io[IO.U_EMER_COOL] ---@type rs_db_dig_io|nil
|
||||||
if io ~= nil then
|
if io ~= nil then
|
||||||
self.db.annunciator.EmergencyCoolant = util.trinary(io.read(), 3, 2)
|
annunc.EmergencyCoolant = util.trinary(io.read(), 3, 2)
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -172,7 +177,7 @@ function logic.update_annunciator(self)
|
|||||||
local boilers_ready = num_boilers == #self.boilers
|
local boilers_ready = num_boilers == #self.boilers
|
||||||
|
|
||||||
-- clear boiler online flags
|
-- clear boiler online flags
|
||||||
for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end
|
for i = 1, num_boilers do annunc.BoilerOnline[i] = false end
|
||||||
|
|
||||||
-- aggregated statistics
|
-- aggregated statistics
|
||||||
local boiler_steam_dt_sum = 0.0
|
local boiler_steam_dt_sum = 0.0
|
||||||
@ -185,7 +190,7 @@ function logic.update_annunciator(self)
|
|||||||
local boiler = session.get_db() ---@type boilerv_session_db
|
local boiler = session.get_db() ---@type boilerv_session_db
|
||||||
local idx = session.get_device_idx()
|
local idx = session.get_device_idx()
|
||||||
|
|
||||||
self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not boiler.formed) or session.is_faulted()
|
annunc.RCSFault = annunc.RCSFault or (not boiler.formed) or session.is_faulted()
|
||||||
|
|
||||||
-- update ready state
|
-- update ready state
|
||||||
-- - must be formed
|
-- - must be formed
|
||||||
@ -199,8 +204,8 @@ function logic.update_annunciator(self)
|
|||||||
boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. idx)
|
boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. idx)
|
||||||
boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. idx)
|
boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. idx)
|
||||||
|
|
||||||
self.db.annunciator.BoilerOnline[idx] = true
|
annunc.BoilerOnline[idx] = true
|
||||||
self.db.annunciator.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow
|
annunc.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check heating rate low
|
-- check heating rate low
|
||||||
@ -214,9 +219,9 @@ function logic.update_annunciator(self)
|
|||||||
local db = boiler.get_db() ---@type boilerv_session_db
|
local db = boiler.get_db() ---@type boilerv_session_db
|
||||||
|
|
||||||
if r_db.mek_status.status then
|
if r_db.mek_status.status then
|
||||||
self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0
|
annunc.HeatingRateLow[idx] = db.state.boil_rate == 0
|
||||||
else
|
else
|
||||||
self.db.annunciator.HeatingRateLow[idx] = false
|
annunc.HeatingRateLow[idx] = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -256,7 +261,7 @@ function logic.update_annunciator(self)
|
|||||||
cfmismatch = cfmismatch or _get_dt(DT_KEYS.ReactorCCool) < -10.0 or (gaining_hc and r_db.mek_status.ccool_fill == 0)
|
cfmismatch = cfmismatch or _get_dt(DT_KEYS.ReactorCCool) < -10.0 or (gaining_hc and r_db.mek_status.ccool_fill == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.db.annunciator.CoolantFeedMismatch = cfmismatch
|
annunc.CoolantFeedMismatch = cfmismatch
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
-- TURBINES --
|
-- TURBINES --
|
||||||
@ -265,7 +270,7 @@ function logic.update_annunciator(self)
|
|||||||
local turbines_ready = num_turbines == #self.turbines
|
local turbines_ready = num_turbines == #self.turbines
|
||||||
|
|
||||||
-- clear turbine online flags
|
-- clear turbine online flags
|
||||||
for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end
|
for i = 1, num_turbines do annunc.TurbineOnline[i] = false end
|
||||||
|
|
||||||
-- aggregated statistics
|
-- aggregated statistics
|
||||||
local total_flow_rate = 0
|
local total_flow_rate = 0
|
||||||
@ -280,7 +285,7 @@ function logic.update_annunciator(self)
|
|||||||
local session = self.turbines[i] ---@type unit_session
|
local session = self.turbines[i] ---@type unit_session
|
||||||
local turbine = session.get_db() ---@type turbinev_session_db
|
local turbine = session.get_db() ---@type turbinev_session_db
|
||||||
|
|
||||||
self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not turbine.formed) or session.is_faulted()
|
annunc.RCSFault = annunc.RCSFault or (not turbine.formed) or session.is_faulted()
|
||||||
|
|
||||||
-- update ready state
|
-- update ready state
|
||||||
-- - must be formed
|
-- - must be formed
|
||||||
@ -295,19 +300,19 @@ function logic.update_annunciator(self)
|
|||||||
max_water_return_rate = max_water_return_rate + turbine.build.max_water_output
|
max_water_return_rate = max_water_return_rate + turbine.build.max_water_output
|
||||||
self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades
|
self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades
|
||||||
|
|
||||||
self.db.annunciator.TurbineOnline[session.get_device_idx()] = true
|
annunc.TurbineOnline[session.get_device_idx()] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine
|
-- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine
|
||||||
self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate)
|
annunc.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate)
|
||||||
|
|
||||||
-- check for steam feed mismatch and max return rate
|
-- check for steam feed mismatch and max return rate
|
||||||
local steam_dt_max = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MaxSteamDT_H20, ANNUNC_LIMS.SFM_MaxSteamDT_NA)
|
local steam_dt_max = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MaxSteamDT_H20, ANNUNC_LIMS.SFM_MaxSteamDT_NA)
|
||||||
local water_dt_min = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MinWaterDT_H20, ANNUNC_LIMS.SFM_MinWaterDT_NA)
|
local water_dt_min = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MinWaterDT_H20, ANNUNC_LIMS.SFM_MinWaterDT_NA)
|
||||||
local sfmismatch = math.abs(total_flow_rate - total_input_rate) > ANNUNC_LIMS.SteamFeedMismatch
|
local sfmismatch = math.abs(total_flow_rate - total_input_rate) > ANNUNC_LIMS.SteamFeedMismatch
|
||||||
sfmismatch = sfmismatch or boiler_steam_dt_sum > steam_dt_max or boiler_water_dt_sum < water_dt_min
|
sfmismatch = sfmismatch or boiler_steam_dt_sum > steam_dt_max or boiler_water_dt_sum < water_dt_min
|
||||||
self.db.annunciator.SteamFeedMismatch = sfmismatch
|
annunc.SteamFeedMismatch = sfmismatch
|
||||||
self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0
|
annunc.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0
|
||||||
|
|
||||||
-- turbine safety checks
|
-- turbine safety checks
|
||||||
for i = 1, #self.turbines do
|
for i = 1, #self.turbines do
|
||||||
@ -317,37 +322,22 @@ function logic.update_annunciator(self)
|
|||||||
|
|
||||||
-- check if steam dumps are open
|
-- check if steam dumps are open
|
||||||
if db.state.dumping_mode == DUMPING_MODE.IDLE then
|
if db.state.dumping_mode == DUMPING_MODE.IDLE then
|
||||||
self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.OK
|
annunc.SteamDumpOpen[idx] = TRI_FAIL.OK
|
||||||
elseif db.state.dumping_mode == DUMPING_MODE.DUMPING_EXCESS then
|
elseif db.state.dumping_mode == DUMPING_MODE.DUMPING_EXCESS then
|
||||||
self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.PARTIAL
|
annunc.SteamDumpOpen[idx] = TRI_FAIL.PARTIAL
|
||||||
else
|
else
|
||||||
self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.FULL
|
annunc.SteamDumpOpen[idx] = TRI_FAIL.FULL
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check if turbines are at max speed but not keeping up
|
-- check if turbines are at max speed but not keeping up
|
||||||
self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0.0)
|
annunc.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0.0)
|
||||||
|
|
||||||
--[[
|
-- see notes at cc-mek-scada/wiki/Annunciator-Panels#Generator-Trip
|
||||||
Generator Trip
|
annunc.GeneratorTrip[idx] = (_get_dt(DT_KEYS.TurbinePower .. idx) > 0.0) and (db.tanks.energy_fill > 0.05)
|
||||||
a generator trip is when a generator suddenly and unexpectedly loses it's external load, which occurs when a power plant
|
|
||||||
is disconnected from the grid. in our case, this is when the turbine is disconnected, or what it's connected to becomes
|
|
||||||
fully charged. this is identified by detecting if:
|
|
||||||
- the internal power storage of the turbine is increasing AND
|
|
||||||
- there is at least 5% energy fill (preventing false trips with periodic power extraction from other mods)
|
|
||||||
this would then mean there is no external load and there will be a turbine trip soon if this is not resolved
|
|
||||||
]]--
|
|
||||||
self.db.annunciator.GeneratorTrip[idx] = (_get_dt(DT_KEYS.TurbinePower .. idx) > 0.0) and (db.tanks.energy_fill > 0.05)
|
|
||||||
|
|
||||||
--[[
|
-- see notes at cc-mek-scada/wiki/Annunciator-Panels#Turbine-Trip
|
||||||
Turbine Trip
|
|
||||||
a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool.
|
|
||||||
this can be identified by these conditions:
|
|
||||||
- the current flow rate is 0 mB/t and it should not be
|
|
||||||
- can initially catch this by detecting a 0 flow rate with a non-zero input rate, but eventually the steam will fill up
|
|
||||||
- can later identified by presence of steam in tank with a 0 flow rate
|
|
||||||
]]--
|
|
||||||
local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01
|
local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01
|
||||||
self.db.annunciator.TurbineTrip[idx] = has_steam and db.state.flow_rate == 0
|
annunc.TurbineTrip[idx] = has_steam and db.state.flow_rate == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- update auto control ready state for this unit
|
-- update auto control ready state for this unit
|
||||||
@ -577,6 +567,7 @@ end
|
|||||||
---@param self _unit_self unit instance
|
---@param self _unit_self unit instance
|
||||||
function logic.update_status_text(self)
|
function logic.update_status_text(self)
|
||||||
local AISTATE = self.types.AISTATE
|
local AISTATE = self.types.AISTATE
|
||||||
|
local annunc = self.db.annunciator
|
||||||
|
|
||||||
-- check if an alarm is active (tripped or ack'd)
|
-- check if an alarm is active (tripped or ack'd)
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@ -666,13 +657,13 @@ function logic.update_status_text(self)
|
|||||||
if plc_db.mek_status.status then
|
if plc_db.mek_status.status then
|
||||||
self.status_text[1] = "ACTIVE"
|
self.status_text[1] = "ACTIVE"
|
||||||
|
|
||||||
if self.db.annunciator.ReactorHighDeltaT then
|
if annunc.ReactorHighDeltaT then
|
||||||
self.status_text[2] = "core temperature rising"
|
self.status_text[2] = "core temperature rising"
|
||||||
elseif self.db.annunciator.ReactorTempHigh then
|
elseif annunc.ReactorTempHigh then
|
||||||
self.status_text[2] = "core temp high, system nominal"
|
self.status_text[2] = "core temp high, system nominal"
|
||||||
elseif self.db.annunciator.FuelInputRateLow then
|
elseif annunc.FuelInputRateLow then
|
||||||
self.status_text[2] = "insufficient fuel input rate"
|
self.status_text[2] = "insufficient fuel input rate"
|
||||||
elseif self.db.annunciator.WasteLineOcclusion then
|
elseif annunc.WasteLineOcclusion then
|
||||||
self.status_text[2] = "insufficient waste output rate"
|
self.status_text[2] = "insufficient waste output rate"
|
||||||
elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then
|
elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then
|
||||||
self.status_text[2] = "awaiting flow stability"
|
self.status_text[2] = "awaiting flow stability"
|
||||||
@ -711,7 +702,7 @@ function logic.update_status_text(self)
|
|||||||
end
|
end
|
||||||
|
|
||||||
self.status_text = { "RPS SCRAM", cause }
|
self.status_text = { "RPS SCRAM", cause }
|
||||||
elseif self.db.annunciator.RadiationWarning then
|
elseif annunc.RadiationWarning then
|
||||||
-- elevated, non-hazardous level of radiation is low priority, so display it now if everything else was fine
|
-- elevated, non-hazardous level of radiation is low priority, so display it now if everything else was fine
|
||||||
self.status_text = { "RADIATION DETECTED", "elevated level of radiation" }
|
self.status_text = { "RADIATION DETECTED", "elevated level of radiation" }
|
||||||
else
|
else
|
||||||
@ -726,7 +717,7 @@ function logic.update_status_text(self)
|
|||||||
self.status_text[2] = "core hot"
|
self.status_text[2] = "core hot"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif self.db.annunciator.RadiationWarning then
|
elseif annunc.RadiationWarning then
|
||||||
-- in case PLC was disconnected but radiation is present
|
-- in case PLC was disconnected but radiation is present
|
||||||
self.status_text = { "RADIATION DETECTED", "elevated level of radiation" }
|
self.status_text = { "RADIATION DETECTED", "elevated level of radiation" }
|
||||||
else
|
else
|
||||||
@ -738,6 +729,7 @@ end
|
|||||||
---@param self _unit_self unit instance
|
---@param self _unit_self unit instance
|
||||||
function logic.handle_redstone(self)
|
function logic.handle_redstone(self)
|
||||||
local AISTATE = self.types.AISTATE
|
local AISTATE = self.types.AISTATE
|
||||||
|
local annunc = self.db.annunciator
|
||||||
|
|
||||||
-- check if an alarm is active (tripped or ack'd)
|
-- check if an alarm is active (tripped or ack'd)
|
||||||
---@nodiscard
|
---@nodiscard
|
||||||
@ -806,7 +798,7 @@ function logic.handle_redstone(self)
|
|||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
local enable_emer_cool = self.plc_cache.rps_status.low_cool or
|
local enable_emer_cool = self.plc_cache.rps_status.low_cool or
|
||||||
(self.auto_engaged and self.db.annunciator.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp))
|
(self.auto_engaged and annunc.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp))
|
||||||
|
|
||||||
-- don't turn off emergency coolant on sufficient coolant level since it might drop again
|
-- don't turn off emergency coolant on sufficient coolant level since it might drop again
|
||||||
-- turn off once system is OK again
|
-- turn off once system is OK again
|
||||||
@ -822,7 +814,7 @@ function logic.handle_redstone(self)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.db.annunciator.EmergencyCoolant > 1 and self.emcool_opened then
|
if annunc.EmergencyCoolant > 1 and self.emcool_opened then
|
||||||
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed"))
|
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed"))
|
||||||
log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam"))
|
log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam"))
|
||||||
end
|
end
|
||||||
@ -849,7 +841,7 @@ function logic.handle_redstone(self)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.db.annunciator.EmergencyCoolant > 1 and not self.emcool_opened then
|
if annunc.EmergencyCoolant > 1 and not self.emcool_opened then
|
||||||
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened"))
|
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened"))
|
||||||
log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam"))
|
log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam"))
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user