Merge branch 'devel' into front-panels

This commit is contained in:
Mikayla Fischler 2023-05-11 20:02:42 -04:00
commit 513c72ea79
62 changed files with 617 additions and 306 deletions

View File

@ -22,11 +22,10 @@ jobs:
uses: lunarmodules/luacheck@v1.1.0
with:
# Argument Explanations
# -a = Disable warning for unused arguments
# -i 121 = Setting a read-only global variable
# 512 = Loop can be executed at most once
# 542 = An empty if branch
# --no-max-line-length = Disable warnings for long line lengths
# --exclude-files ... = Exclude lockbox library (external) and config files
# --globals ... = Override all globals overridden in .vscode/settings.json AND 'os' since CraftOS 'os' differs from Lua's 'os'
args: . --no-max-line-length -a -i 121 512 542 --exclude-files ./lockbox/* ./*/config.lua --globals os _HOST bit colors fs http parallel periphemu peripheral read rs settings shell term textutils window
args: . --no-max-line-length -i 121 512 542 --exclude-files ./lockbox/* ./*/config.lua --globals os _HOST bit colors fs http parallel periphemu peripheral read rs settings shell term textutils window

View File

@ -28,5 +28,7 @@ config.LOG_PATH = "/log.txt"
-- 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
return config

View File

@ -18,11 +18,15 @@ local iocontrol = {}
---@class ioctl
local io = {}
-- luacheck: no unused args
-- placeholder acknowledge function for type hinting
---@param success boolean
---@diagnostic disable-next-line: unused-local
local function __generic_ack(success) end
-- luacheck: unused args
-- initialize the coordinator IO controller
---@param conf facility_conf configuration
---@param comms coord_comms comms reference

View File

@ -163,9 +163,9 @@ end
function renderer.ui_ready() return engine.ui_ready end
-- handle a touch event
---@param event mouse_interaction
---@param event mouse_interaction|nil
function renderer.handle_mouse(event)
if engine.ui_ready then
if engine.ui_ready and event ~= nil then
if event.monitor == engine.monitors.primary_name then
engine.ui.main_display.handle_mouse(event)
else

View File

@ -20,7 +20,7 @@ local sounder = require("coordinator.sounder")
local apisessions = require("coordinator.session.apisessions")
local COORDINATOR_VERSION = "v0.13.8"
local COORDINATOR_VERSION = "v0.14.0"
local println = util.println
local println_ts = util.println_ts
@ -50,6 +50,7 @@ cfv.assert_type_num(config.SOUNDER_VOLUME)
cfv.assert_type_bool(config.TIME_24_HOUR)
cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE)
cfv.assert_type_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -57,7 +58,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields")
-- log init
----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE)
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG)
log.info("========================================")
log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION)
@ -358,7 +359,7 @@ local function main()
end
elseif event == "monitor_touch" then
-- handle a monitor touch event
renderer.handle_mouse(core.events.touch(param1, param2, param3))
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
elseif event == "speaker_audio_empty" then
-- handle speaker buffer emptied
sounder.continue()

View File

@ -9,8 +9,8 @@ local DataIndicator = require("graphics.elements.indicators.data")
local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.graphics.cpair
local border = core.graphics.border
local cpair = core.cpair
local border = core.border
-- new boiler view
---@param root graphics_element parent

View File

@ -13,10 +13,10 @@ local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.graphics.cpair
local border = core.graphics.border
local cpair = core.cpair
local border = core.border
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
-- new induction matrix view
---@param root graphics_element parent

View File

@ -21,10 +21,10 @@ local HazardButton = require("graphics.elements.controls.hazard_button")
local RadioButton = require("graphics.elements.controls.radio_button")
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.graphics.cpair
local border = core.graphics.border
local cpair = core.cpair
local border = core.border
local period = core.flasher.PERIOD

View File

@ -11,8 +11,8 @@ local DataIndicator = require("graphics.elements.indicators.data")
local HorizontalBar = require("graphics.elements.indicators.hbar")
local StateIndicator = require("graphics.elements.indicators.state")
local cpair = core.graphics.cpair
local border = core.graphics.border
local cpair = core.cpair
local border = core.border
-- create new reactor view
---@param root graphics_element parent

View File

@ -12,8 +12,8 @@ local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.graphics.cpair
local border = core.graphics.border
local cpair = core.cpair
local border = core.border
-- new turbine view
---@param root graphics_element parent

View File

@ -26,10 +26,10 @@ local PushButton = require("graphics.elements.controls.push_button")
local RadioButton = require("graphics.elements.controls.radio_button")
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.graphics.cpair
local border = core.graphics.border
local cpair = core.cpair
local border = core.border
local period = core.flasher.PERIOD

View File

@ -14,9 +14,9 @@ local Div = require("graphics.elements.div")
local PipeNetwork = require("graphics.elements.pipenet")
local TextBox = require("graphics.elements.textbox")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local pipe = core.graphics.pipe
local pipe = core.pipe
-- make a new unit overview window
---@param parent graphics_element parent

View File

@ -18,9 +18,9 @@ local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.graphics.cpair
local cpair = core.cpair
-- create new main view
---@param main graphics_element main displaybox

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -1,96 +1,19 @@
--
-- Graphics Core Functions and Objects
-- Graphics Core Types, Checks, and Constructors
--
local events = require("graphics.events")
local flasher = require("graphics.flasher")
local core = {}
local flasher = require("graphics.flasher")
core.flasher = flasher
local events = {}
---@enum click_type
events.click_type = {
VIRTUAL = 0,
LEFT_BUTTON = 1,
RIGHT_BUTTON = 2,
MID_BUTTON = 3
}
---@class mouse_interaction
---@field monitor string
---@field button integer
---@field x integer
---@field y integer
-- create a new monitor touch mouse interaction event
---@nodiscard
---@param monitor string
---@param x integer
---@param y integer
---@return mouse_interaction
function events.touch(monitor, x, y)
return {
monitor = monitor,
button = events.click_type.LEFT_BUTTON,
x = x,
y = y
}
end
-- create a new mouse click mouse interaction event
---@nodiscard
---@param button click_type
---@param x integer
---@param y integer
---@return mouse_interaction
function events.click(button, x, y)
return {
monitor = "terminal",
button = button,
x = x,
y = y
}
end
-- create a new transposed mouse interaction event using the event's monitor/button fields
---@nodiscard
---@param event mouse_interaction
---@param new_x integer
---@param new_y integer
---@return mouse_interaction
function events.mouse_transposed(event, new_x, new_y)
return {
monitor = event.monitor,
button = event.button,
x = new_x,
y = new_y
}
end
-- create a new generic mouse interaction event
---@nodiscard
---@param monitor string
---@param button click_type
---@param x integer
---@param y integer
---@return mouse_interaction
function events.mouse_generic(monitor, button, x, y)
return {
monitor = monitor,
button = button,
x = x,
y = y
}
end
core.events = events
local graphics = {}
-- Core Types
---@enum TEXT_ALIGN
graphics.TEXT_ALIGN = {
core.TEXT_ALIGN = {
LEFT = 1,
CENTER = 2,
RIGHT = 3
@ -109,7 +32,7 @@ graphics.TEXT_ALIGN = {
---@param color color border color
---@param even? boolean whether to pad width extra to account for rectangular pixels, defaults to false
---@return graphics_border
function graphics.border(width, color, even)
function core.border(width, color, even)
return {
width = width,
color = color,
@ -130,7 +53,7 @@ end
---@param w integer
---@param h integer
---@return graphics_frame
function graphics.gframe(x, y, w, h)
function core.gframe(x, y, w, h)
return {
x = x,
y = y,
@ -154,7 +77,7 @@ end
---@param a color
---@param b color
---@return cpair
function graphics.cpair(a, b)
function core.cpair(a, b)
return {
-- color pairs
color_a = a,
@ -191,7 +114,7 @@ end
---@param thin? boolean true for 1 subpixel, false (default) for 2
---@param align_tr? boolean false to align bottom left (default), true to align top right
---@return pipe
function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr)
function core.pipe(x1, y1, x2, y2, color, thin, align_tr)
return {
x1 = x1,
y1 = y1,
@ -205,6 +128,4 @@ function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr)
}
end
core.graphics = graphics
return core

View File

@ -28,6 +28,7 @@ local element = {}
---|sidebar_args
---|spinbox_args
---|switch_button_args
---|tabbar_args
---|alarm_indicator_light
---|core_map_args
---|data_indicator_args
@ -59,10 +60,10 @@ function element.new(args)
id = -1,
elem_type = debug.getinfo(2).name,
define_completed = false,
p_window = nil, ---@type table
position = { x = 1, y = 1 },
p_window = nil, ---@type table
position = { x = 1, y = 1 }, ---@type coordinate_2d
child_offset = { x = 0, y = 0 },
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1},
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
next_y = 1,
children = {},
mt = {}
@ -73,15 +74,26 @@ function element.new(args)
enabled = true,
value = nil, ---@type any
window = nil, ---@type table
fg_bg = core.graphics.cpair(colors.white, colors.black),
frame = core.graphics.gframe(1, 1, 1, 1)
fg_bg = core.cpair(colors.white, colors.black),
frame = core.gframe(1, 1, 1, 1)
}
local name_brief = "graphics.element{" .. self.elem_type .. "}: "
-- element as string
function self.mt.__tostring()
return "graphics.element{" .. self.elem_type .. "} @ " .. tostring(self)
end
-- check if a coordinate is within the bounds of this element
---@param x integer
---@param y integer
local function _in_bounds(x, y)
local in_x = x >= self.bounds.x1 and x <= self.bounds.x2
local in_y = y >= self.bounds.y1 and y <= self.bounds.y2
return in_x and in_y
end
---@class graphics_element
local public = {}
@ -138,10 +150,10 @@ function element.new(args)
end
-- check frame
assert(f.x >= 1, "graphics.element{" .. self.elem_type .. "}: frame x not >= 1")
assert(f.y >= 1, "graphics.element{" .. self.elem_type .. "}: frame y not >= 1")
assert(f.w >= 1, "graphics.element{" .. self.elem_type .. "}: frame width not >= 1")
assert(f.h >= 1, "graphics.element{" .. self.elem_type .. "}: frame height not >= 1")
assert(f.x >= 1, name_brief .. "frame x not >= 1")
assert(f.y >= 1, name_brief .. "frame y not >= 1")
assert(f.w >= 1, name_brief .. "frame width not >= 1")
assert(f.h >= 1, name_brief .. "frame height not >= 1")
-- create window
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true)
@ -168,6 +180,7 @@ function element.new(args)
self.bounds.y2 = self.position.y + f.h - 1
end
-- luacheck: push ignore
---@diagnostic disable: unused-local, unused-vararg
-- handle a mouse event
@ -224,6 +237,7 @@ function element.new(args)
function protected.resize(...)
end
-- luacheck: pop
---@diagnostic enable: unused-local, unused-vararg
-- start animations
@ -250,7 +264,7 @@ function element.new(args)
end
-- check window
assert(self.p_window, "graphics.element{" .. self.elem_type .. "}: no parent window provided")
assert(self.p_window, name_brief .. "no parent window provided")
-- prepare the template
if args.parent == nil then
@ -419,17 +433,18 @@ function element.new(args)
-- handle a monitor touch or mouse click
---@param event mouse_interaction mouse interaction event
function public.handle_mouse(event)
local in_x = event.x >= self.bounds.x1 and event.x <= self.bounds.x2
local in_y = event.y >= self.bounds.y1 and event.y <= self.bounds.y2
local x_ini, y_ini, x_cur, y_cur = event.initial.x, event.initial.y, event.current.x, event.current.y
if in_x and in_y then
local event_T = core.events.mouse_transposed(event, (event.x - self.position.x) + 1, (event.y - self.position.y) + 1)
local ini_in = _in_bounds(x_ini, y_ini)
local cur_in = _in_bounds(x_cur, y_cur)
-- handle the touch event, transformed into the window frame
if ini_in then
local event_T = core.events.mouse_transposed(event, self.position.x, self.position.y)
if not cur_in then event_T.type = core.events.CLICK_TYPE.EXITED end
-- handle the mouse event then pass to children
protected.handle_mouse(event_T)
-- pass on touch event to children
for _, val in pairs(self.children) do val.handle_mouse(event_T) end
for _, child in pairs(self.children) do child.handle_mouse(event_T) end
end
end

View File

@ -142,24 +142,25 @@ local function hazard_button(args)
-- handle mouse interaction
---@param event mouse_interaction mouse event
---@diagnostic disable-next-line: unused-local
function e.handle_mouse(event)
if e.enabled then
-- change text color to indicate clicked
e.window.setTextColor(args.accent)
e.window.setCursorPos(3, 2)
e.window.write(args.text)
if core.events.was_clicked(event.type) then
-- change text color to indicate clicked
e.window.setTextColor(args.accent)
e.window.setCursorPos(3, 2)
e.window.write(args.text)
-- abort any other callbacks
tcd.abort(on_timeout)
tcd.abort(on_success)
tcd.abort(on_failure)
-- abort any other callbacks
tcd.abort(on_timeout)
tcd.abort(on_success)
tcd.abort(on_failure)
-- 1.5 second timeout
tcd.dispatch(1.5, on_timeout)
-- 1.5 second timeout
tcd.dispatch(1.5, on_timeout)
-- call the touch callback
args.callback()
-- call the touch callback
args.callback()
end
end
end
@ -167,18 +168,13 @@ local function hazard_button(args)
---@param result boolean true for success, false for failure
function e.response_callback(result)
tcd.abort(on_timeout)
if result then
on_success()
else
on_failure(0)
end
if result then on_success() else on_failure(0) end
end
-- set the value (true simulates pressing the button)
---@param val boolean new value
function e.set_value(val)
if val then e.handle_mouse(core.events.mouse_generic("", core.events.click_type.VIRTUAL, 1, 1)) end
if val then e.handle_mouse(core.events.mouse_generic(core.events.CLICK_TYPE.UP, 1, 1)) end
end
-- show the button as disabled

View File

@ -2,13 +2,13 @@
local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element")
---@class button_option
---@field text string
---@field fg_bg cpair
---@field active_fg_bg cpair
---@field _lpad integer automatically calculated left pad
---@field _start_x integer starting touch x range (inclusive)
---@field _end_x integer ending touch x range (inclusive)
@ -62,9 +62,7 @@ local function multi_button(args)
local next_x = 2
for i = 1, #args.options do
local opt = args.options[i] ---@type button_option
local w = string.len(opt.text)
opt._lpad = math.floor((e.frame.w - w) / 2)
opt._start_x = next_x
opt._end_x = next_x + button_width - 1
@ -92,20 +90,32 @@ local function multi_button(args)
end
end
-- check which button a given x is within
---@return integer|nil button index or nil if not within a button
local function which_button(x)
for i = 1, #args.options do
local opt = args.options[i] ---@type button_option
if x >= opt._start_x and x <= opt._end_x then return i end
end
return nil
end
-- handle mouse interaction
---@param event mouse_interaction mouse event
---@diagnostic disable-next-line: unused-local
function e.handle_mouse(event)
-- determine what was pressed
if e.enabled and event.y == 1 then
for i = 1, #args.options do
local opt = args.options[i] ---@type button_option
-- if enabled and the button row was pressed...
if e.enabled and core.events.was_clicked(event.type) then
-- a button may have been pressed, which one was it?
local button_ini = which_button(event.initial.x)
local button_cur = which_button(event.current.x)
if event.x >= opt._start_x and event.x <= opt._end_x then
e.value = i
draw()
args.callback(e.value)
end
-- mouse up must always have started with a mouse down on the same button to count as a click
-- tap always has identical coordinates, so this always passes for taps
if button_ini == button_cur and button_cur ~= nil then
e.value = button_cur
draw()
args.callback(e.value)
end
end
end

View File

@ -5,6 +5,8 @@ local tcd = require("scada-common.tcallbackdsp")
local core = require("graphics.core")
local element = require("graphics.element")
local CLICK_TYPE = core.events.CLICK_TYPE
---@class push_button_args
---@field text string button text
---@field callback function function to call on touch
@ -24,6 +26,8 @@ local element = require("graphics.element")
local function push_button(args)
assert(type(args.text) == "string", "graphics.elements.controls.push_button: text is a required field")
assert(type(args.callback) == "function", "graphics.elements.controls.push_button: callback is a required field")
assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0),
"graphics.elements.controls.push_button: min_width must be nil or a number > 0")
local text_width = string.len(args.text)
@ -47,38 +51,50 @@ local function push_button(args)
e.window.write(args.text)
end
-- draw the button as pressed (if active_fg_bg set)
local function show_pressed()
if e.enabled and args.active_fg_bg ~= nil then
e.value = true
e.window.setTextColor(args.active_fg_bg.fgd)
e.window.setBackgroundColor(args.active_fg_bg.bkg)
draw()
end
end
-- draw the button as unpressed (if active_fg_bg set)
local function show_unpressed()
if e.enabled and args.active_fg_bg ~= nil then
e.value = false
e.window.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg)
draw()
end
end
-- handle mouse interaction
---@param event mouse_interaction mouse event
---@diagnostic disable-next-line: unused-local
function e.handle_mouse(event)
if e.enabled then
if args.active_fg_bg ~= nil then
-- show as pressed
e.value = true
e.window.setTextColor(args.active_fg_bg.fgd)
e.window.setBackgroundColor(args.active_fg_bg.bkg)
draw()
if event.type == CLICK_TYPE.TAP then
show_pressed()
-- show as unpressed in 0.25 seconds
tcd.dispatch(0.25, function ()
e.value = false
if e.enabled then
e.window.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg)
end
draw()
end)
if args.active_fg_bg ~= nil then tcd.dispatch(0.25, show_unpressed) end
args.callback()
elseif event.type == CLICK_TYPE.DOWN then
show_pressed()
elseif event.type == CLICK_TYPE.UP then
show_unpressed()
args.callback()
elseif event.type == CLICK_TYPE.EXITED then
show_unpressed()
end
-- call the touch callback
args.callback()
end
end
-- set the value (true simulates pressing the button)
---@param val boolean new value
function e.set_value(val)
if val then e.handle_mouse(core.events.mouse_generic("", core.events.click_type.VIRTUAL, 1, 1)) end
if val then e.handle_mouse(core.events.mouse_generic(core.events.CLICK_TYPE.UP, 1, 1)) end
end
-- show butten as enabled

View File

@ -1,5 +1,6 @@
-- Radio Button Graphics Element
local core = require("graphics.core")
local element = require("graphics.element")
---@class radio_button_args
@ -82,10 +83,10 @@ local function radio_button(args)
-- handle mouse interaction
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
-- determine what was pressed
if e.enabled then
if args.options[event.y] ~= nil then
e.value = event.y
if e.enabled and core.events.was_clicked(event.type) and (event.initial.y == event.current.y) then
-- determine what was pressed
if args.options[event.current.y] ~= nil then
e.value = event.current.y
draw()
args.callback(e.value)
end

View File

@ -2,8 +2,11 @@
local tcd = require("scada-common.tcallbackdsp")
local core = require("graphics.core")
local element = require("graphics.element")
local CLICK_TYPE = core.events.CLICK_TYPE
---@class sidebar_tab
---@field char string character identifier
---@field color cpair tab colors (fg/bg)
@ -39,7 +42,10 @@ local function sidebar(args)
-- show the button state
---@param pressed boolean if the currently selected tab should appear as actively pressed
local function draw(pressed)
---@param pressed_idx? integer optional index to show as held (that is not yet selected)
local function draw(pressed, pressed_idx)
pressed_idx = pressed_idx or e.value
for i = 1, #args.tabs do
local tab = args.tabs[i] ---@type sidebar_tab
@ -47,7 +53,7 @@ local function sidebar(args)
e.window.setCursorPos(1, y)
if pressed and e.value == i then
if pressed and i == pressed_idx then
e.window.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg)
else
@ -74,16 +80,27 @@ local function sidebar(args)
function e.handle_mouse(event)
-- determine what was pressed
if e.enabled then
local idx = math.ceil(event.y / 3)
local cur_idx = math.ceil(event.current.y / 3)
local ini_idx = math.ceil(event.initial.y / 3)
if args.tabs[idx] ~= nil then
e.value = idx
draw(true)
-- show as unpressed in 0.25 seconds
tcd.dispatch(0.25, function () draw(false) end)
args.callback(e.value)
if args.tabs[cur_idx] ~= nil then
if event.type == CLICK_TYPE.TAP then
e.value = cur_idx
draw(true)
-- show as unpressed in 0.25 seconds
tcd.dispatch(0.25, function () draw(false) end)
args.callback(e.value)
elseif event.type == CLICK_TYPE.DOWN then
draw(true, cur_idx)
elseif event.type == CLICK_TYPE.UP then
if cur_idx == ini_idx then
e.value = cur_idx
draw(false)
args.callback(e.value)
else draw(false) end
elseif event.type == CLICK_TYPE.EXITED then
draw(false)
end
end
end
end

View File

@ -2,6 +2,7 @@
local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element")
---@class spinbox_args
@ -130,19 +131,22 @@ local function spinbox(args)
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
-- only handle if on an increment or decrement arrow
if e.enabled and event.x ~= dec_point_x then
local idx = util.trinary(event.x > dec_point_x, event.x - 1, event.x)
if digits[idx] ~= nil then
if event.y == 1 then
-- increment
digits[idx] = digits[idx] + 1
elseif event.y == 3 then
-- decrement
digits[idx] = digits[idx] - 1
end
if e.enabled and core.events.was_clicked(event.type) and
(event.current.x ~= dec_point_x) and (event.current.y ~= 2) then
if event.current.x == event.initial.x and event.current.y == event.initial.y then
local idx = util.trinary(event.current.x > dec_point_x, event.current.x - 1, event.current.x)
if digits[idx] ~= nil then
if event.current.y == 1 then
-- increment
digits[idx] = digits[idx] + 1
elseif event.current.y == 3 then
-- decrement
digits[idx] = digits[idx] - 1
end
update_value()
show_num()
update_value()
show_num()
end
end
end
end

View File

@ -1,5 +1,6 @@
-- Button Graphics Element
local core = require("graphics.core")
local element = require("graphics.element")
---@class switch_button_args
@ -22,13 +23,15 @@ local function switch_button(args)
assert(type(args.text) == "string", "graphics.elements.controls.switch_button: text is a required field")
assert(type(args.callback) == "function", "graphics.elements.controls.switch_button: callback is a required field")
assert(type(args.active_fg_bg) == "table", "graphics.elements.controls.switch_button: active_fg_bg is a required field")
assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0),
"graphics.elements.controls.switch_button: min_width must be nil or a number > 0")
-- single line
args.height = 1
-- determine widths
local text_width = string.len(args.text)
args.width = math.max(text_width + 2, args.min_width)
-- single line height, calculate width
args.height = 1
args.min_width = args.min_width or 0
args.width = math.max(text_width, args.min_width)
-- create new graphics element base object
local e = element.new(args)
@ -64,9 +67,8 @@ local function switch_button(args)
-- handle mouse interaction
---@param event mouse_interaction mouse event
---@diagnostic disable-next-line: unused-local
function e.handle_mouse(event)
if e.enabled then
if e.enabled and core.events.was_clicked(event.type) then
-- toggle state
e.value = not e.value
draw_state()

View File

@ -0,0 +1,130 @@
-- Tab Bar Graphics Element
local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element")
---@class tabbar_tab
---@field name string tab name
---@field color cpair tab colors (fg/bg)
---@field _start_x integer starting touch x range (inclusive)
---@field _end_x integer ending touch x range (inclusive)
---@class tabbar_args
---@field tabs table tab options
---@field callback function function to call on tab change
---@field min_width? integer text length + 2 if omitted
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field width? integer parent width if omitted
---@field fg_bg? cpair foreground/background colors
-- new tab selector
---@param args tabbar_args
---@return graphics_element element, element_id id
local function tabbar(args)
assert(type(args.tabs) == "table", "graphics.elements.controls.tabbar: tabs is a required field")
assert(#args.tabs > 0, "graphics.elements.controls.tabbar: at least one tab is required")
assert(type(args.callback) == "function", "graphics.elements.controls.tabbar: callback is a required field")
assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0),
"graphics.elements.controls.tabbar: min_width must be nil or a number > 0")
-- always 1 tall
args.height = 1
-- determine widths
local max_width = 1
for i = 1, #args.tabs do
local opt = args.tabs[i] ---@type tabbar_tab
if string.len(opt.name) > max_width then
max_width = string.len(opt.name)
end
end
local button_width = math.max(max_width, args.min_width or 0)
-- create new graphics element base object
local e = element.new(args)
assert(e.frame.w >= (button_width * #args.tabs), "graphics.elements.controls.tabbar: width insufficent to display all tabs")
-- default to 1st tab
e.value = 1
-- calculate required tab dimension information
local next_x = 1
for i = 1, #args.tabs do
local tab = args.tabs[i] ---@type tabbar_tab
tab._start_x = next_x
tab._end_x = next_x + button_width - 1
next_x = next_x + button_width
end
-- show the tab state
local function draw()
for i = 1, #args.tabs do
local tab = args.tabs[i] ---@type tabbar_tab
e.window.setCursorPos(tab._start_x, 1)
if e.value == i then
e.window.setTextColor(tab.color.fgd)
e.window.setBackgroundColor(tab.color.bkg)
else
e.window.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg)
end
e.window.write(util.pad(tab.name, button_width))
end
end
-- check which tab a given x is within
---@return integer|nil button index or nil if not within a tab
local function which_tab(x)
for i = 1, #args.tabs do
local tab = args.tabs[i] ---@type tabbar_tab
if x >= tab._start_x and x <= tab._end_x then return i end
end
return nil
end
-- handle mouse interaction
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
-- determine what was pressed
if e.enabled and core.events.was_clicked(event.type) then
-- a button may have been pressed, which one was it?
local tab_ini = which_tab(event.initial.x)
local tab_cur = which_tab(event.current.x)
-- mouse up must always have started with a mouse down on the same tab to count as a click
-- tap always has identical coordinates, so this always passes for taps
if tab_ini == tab_cur and tab_cur ~= nil then
e.value = tab_cur
draw()
args.callback(e.value)
end
end
end
-- set the value
---@param val integer new value
function e.set_value(val)
e.value = val
draw()
end
-- initial draw
draw()
return e.get()
end
return tabbar

View File

@ -26,7 +26,7 @@ local function core_map(args)
args.height = 18
-- inherit only foreground color
args.fg_bg = core.graphics.cpair(args.parent.get_fg_bg().fgd, colors.gray)
args.fg_bg = core.cpair(args.parent.get_fg_bg().fgd, colors.gray)
-- create new graphics element base object
local e = element.new(args)

View File

@ -37,7 +37,7 @@ local function pipenet(args)
args.y = args.y or 1
if args.bg ~= nil then
args.fg_bg = core.graphics.cpair(args.bg, args.bg)
args.fg_bg = core.cpair(args.bg, args.bg)
end
-- create new graphics element base object
@ -55,7 +55,7 @@ local function pipenet(args)
e.window.setCursorPos(x, y)
local c = core.graphics.cpair(pipe.color, e.fg_bg.bkg)
local c = core.cpair(pipe.color, e.fg_bg.bkg)
if pipe.align_tr then
-- cross width then height

View File

@ -5,7 +5,7 @@ local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
---@class textbox_args
---@field text string text to show

162
graphics/events.lua Normal file
View File

@ -0,0 +1,162 @@
--
-- Graphics Events and Event Handlers
--
local util = require("scada-common.util")
local events = {}
---@enum CLICK_BUTTON
events.CLICK_BUTTON = {
GENERIC = 0,
LEFT_BUTTON = 1,
RIGHT_BUTTON = 2,
MID_BUTTON = 3
}
---@enum CLICK_TYPE
events.CLICK_TYPE = {
TAP = 1, -- screen tap (complete click)
DOWN = 2, -- button down
UP = 3, -- button up (completed a click)
DRAG = 4, -- mouse dragged
SCROLL_DOWN = 5, -- scroll down
SCROLL_UP = 6, -- scroll up
EXITED = 7 -- cursor exited bounds of element
}
-- create a new 2D coordinate
---@param x integer
---@param y integer
---@return coordinate_2d
local function _coord2d(x, y) return { x = x, y = y } end
---@class mouse_interaction
---@field monitor string
---@field button CLICK_BUTTON
---@field type CLICK_TYPE
---@field initial coordinate_2d
---@field current coordinate_2d
local handler = {
-- left, right, middle button down tracking
button_down = {
_coord2d(0, 0),
_coord2d(0, 0),
_coord2d(0, 0)
}
}
-- create a new monitor touch mouse interaction event
---@nodiscard
---@param monitor string
---@param x integer
---@param y integer
---@return mouse_interaction
local function _monitor_touch(monitor, x, y)
return {
monitor = monitor,
button = events.CLICK_BUTTON.GENERIC,
type = events.CLICK_TYPE.TAP,
initial = _coord2d(x, y),
current = _coord2d(x, y)
}
end
-- create a new mouse button mouse interaction event
---@nodiscard
---@param button CLICK_BUTTON mouse button
---@param type CLICK_TYPE click type
---@param x1 integer initial x
---@param y1 integer initial y
---@param x2 integer current x
---@param y2 integer current y
---@return mouse_interaction
local function _mouse_event(button, type, x1, y1, x2, y2)
return {
monitor = "terminal",
button = button,
type = type,
initial = _coord2d(x1, y1),
current = _coord2d(x2, y2)
}
end
-- create a new generic mouse interaction event
---@nodiscard
---@param type CLICK_TYPE
---@param x integer
---@param y integer
---@return mouse_interaction
function events.mouse_generic(type, x, y)
return {
monitor = "",
button = events.CLICK_BUTTON.GENERIC,
type = type,
initial = _coord2d(x, y),
current = _coord2d(x, y)
}
end
-- create a new transposed mouse interaction event using the event's monitor/button fields
---@nodiscard
---@param event mouse_interaction
---@param elem_pos_x integer element's x position: new x = (event x - element x) + 1
---@param elem_pos_y integer element's y position: new y = (event y - element y) + 1
---@return mouse_interaction
function events.mouse_transposed(event, elem_pos_x, elem_pos_y)
return {
monitor = event.monitor,
button = event.button,
type = event.type,
initial = _coord2d((event.initial.x - elem_pos_x) + 1, (event.initial.y - elem_pos_y) + 1),
current = _coord2d((event.current.x - elem_pos_x) + 1, (event.current.y - elem_pos_y) + 1)
}
end
-- check if an event qualifies as a click (tap or up)
---@nodiscard
---@param t CLICK_TYPE
function events.was_clicked(t) return t == events.CLICK_TYPE.TAP or t == events.CLICK_TYPE.UP end
-- create a new mouse event to pass onto graphics renderer<br>
-- supports: mouse_click, mouse_up, mouse_drag, mouse_scroll, and monitor_touch
---@param event_type os_event OS event to handle
---@param opt integer|string button, scroll direction, or monitor for monitor touch
---@param x integer x coordinate
---@param y integer y coordinate
---@return mouse_interaction|nil
function events.new_mouse_event(event_type, opt, x, y)
if event_type == "mouse_click" then
---@cast opt 1|2|3
handler.button_down[opt] = _coord2d(x, y)
return _mouse_event(opt, events.CLICK_TYPE.DOWN, x, y, x, y)
elseif event_type == "mouse_up" then
---@cast opt 1|2|3
local initial = handler.button_down[opt] ---@type coordinate_2d
return _mouse_event(opt, events.CLICK_TYPE.UP, initial.x, initial.y, x, y)
elseif event_type == "monitor_touch" then
---@cast opt string
return _monitor_touch(opt, x, y)
elseif event_type == "mouse_drag" then
---@cast opt 1|2|3
local initial = handler.button_down[opt] ---@type coordinate_2d
return _mouse_event(opt, events.CLICK_TYPE.DRAG, initial.x, initial.y, x, y)
elseif event_type == "mouse_scroll" then
---@cast opt 1|-1
local scroll_direction = util.trinary(opt == 1, events.CLICK_TYPE.SCROLL_DOWN, events.CLICK_TYPE.SCROLL_UP)
return _mouse_event(events.CLICK_BUTTON.GENERIC, scroll_direction, x, y, x, y)
end
end
-- create a new key event to pass onto graphics renderer<br>
-- supports: char, key, and key_up
---@param event_type os_event
function events.new_key_event(event_type)
if event_type == "char" then
elseif event_type == "key" then
elseif event_type == "key_up" then
end
end
return events

File diff suppressed because one or more lines are too long

View File

@ -17,5 +17,7 @@ config.LOG_PATH = "/log.txt"
-- 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
return config

View File

@ -70,9 +70,9 @@ end
function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event
---@param event mouse_interaction
---@param event mouse_interaction|nil
function renderer.handle_mouse(event)
if ui.display ~= nil then
if ui.display ~= nil and event ~= nil then
ui.display.handle_mouse(event)
end
end

View File

@ -36,6 +36,7 @@ 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_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -43,7 +44,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields")
-- log init
----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE)
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG)
log.info("========================================")
log.info("BOOTING pocket.startup " .. POCKET_VERSION)
@ -151,9 +152,9 @@ local function main()
-- got a packet
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
pocket_comms.handle_packet(packet)
elseif event == "mouse_click" then
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
-- handle a monitor touch event
renderer.handle_mouse(core.events.touch(param1, param2, param3))
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
end
-- check for termination request

View File

@ -11,9 +11,9 @@ local TextBox = require("graphics.elements.textbox")
local WaitingAnim = require("graphics.elements.animations.waiting")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.graphics.cpair
local cpair = core.cpair
-- create a waiting view
---@param parent graphics_element parent

View File

@ -22,9 +22,9 @@ local TextBox = require("graphics.elements.textbox")
local Sidebar = require("graphics.elements.controls.sidebar")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.graphics.cpair
local cpair = core.cpair
-- create new main view
---@param main graphics_element main displaybox

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.graphics.cpair
-- local cpair = core.cpair
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
-- new boiler page view
---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.graphics.cpair
-- local cpair = core.cpair
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
-- new home page view
---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.graphics.cpair
-- local cpair = core.cpair
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
-- new reactor page view
---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.graphics.cpair
-- local cpair = core.cpair
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
-- new turbine page view
---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
-- local cpair = core.graphics.cpair
-- local cpair = core.cpair
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
-- new unit page view
---@param root graphics_element parent

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -24,5 +24,7 @@ config.LOG_PATH = "/log.txt"
-- 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
return config

View File

@ -22,10 +22,10 @@ local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.graphics.cpair
local border = core.graphics.border
local cpair = core.cpair
local border = core.border
-- create new main view
---@param panel graphics_element main displaybox

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -70,9 +70,9 @@ end
function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event
---@param event mouse_interaction
---@param event mouse_interaction|nil
function renderer.handle_mouse(event)
if ui.display ~= nil then
if ui.display ~= nil and event ~= nil then
ui.display.handle_mouse(event)
end
end

View File

@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
local renderer = require("reactor-plc.renderer")
local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "v1.1.17"
local R_PLC_VERSION = "v1.2.0"
local println = util.println
local println_ts = util.println_ts
@ -38,6 +38,7 @@ 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_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -54,7 +55,7 @@ end
-- log init
----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE)
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG)
log.info("========================================")
log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION)

View File

@ -257,9 +257,9 @@ function threads.thread__main(smem, init)
-- update indicators
databus.tx_hw_status(plc_state)
elseif event == "mouse_click" then
-- handle a monitor touch event
renderer.handle_mouse(core.events.click(param1, param2, param3))
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
-- handle a mouse event
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
elseif event == "clock_start" then
-- start loop clock
loop_clock.start()

View File

@ -17,6 +17,8 @@ config.LOG_PATH = "/log.txt"
-- 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 = {

View File

@ -16,9 +16,9 @@ local TextBox = require("graphics.elements.textbox")
local LED = require("graphics.elements.indicators.led")
local RGBLED = require("graphics.elements.indicators.ledrgb")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.graphics.cpair
local cpair = core.cpair
local UNIT_TYPE_LABELS = {
"UNKNOWN",

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -71,9 +71,9 @@ end
function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event
---@param event mouse_interaction
---@param event mouse_interaction|nil
function renderer.handle_mouse(event)
if ui.display ~= nil then
if ui.display ~= nil and event ~= nil then
ui.display.handle_mouse(event)
end
end

View File

@ -28,7 +28,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
local sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.0.6"
local RTU_VERSION = "v1.1.1"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
@ -49,6 +49,7 @@ 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_bool(config.LOG_DEBUG)
cfv.assert_type_table(config.RTU_DEVICES)
cfv.assert_type_table(config.RTU_REDSTONE)
assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -57,7 +58,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields")
-- log init
----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE)
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG)
log.info("========================================")
log.info("BOOTING rtu.startup " .. RTU_VERSION)

View File

@ -229,9 +229,9 @@ function threads.thread__main(smem)
end
end
end
elseif event == "mouse_click" then
-- handle a monitor touch event
renderer.handle_mouse(core.events.click(param1, param2, param3))
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
-- handle a mouse event
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
end
-- check for termination request

View File

@ -15,12 +15,10 @@ local MODE = {
log.MODE = MODE
-- whether to log debug messages or not
local LOG_DEBUG = true
local log_sys = {
local logger = {
path = "/log.txt",
mode = MODE.APPEND,
debug = false,
file = nil,
dmesg_out = nil
}
@ -41,8 +39,8 @@ local function _log(msg)
-- attempt to write log
local status, result = pcall(function ()
log_sys.file.writeLine(stamped)
log_sys.file.flush()
logger.file.writeLine(stamped)
logger.file.flush()
end)
-- if we don't have space, we need to create a new log file
@ -57,18 +55,18 @@ local function _log(msg)
end
end
if out_of_space or (free_space(log_sys.path) < 100) then
if out_of_space or (free_space(logger.path) < 100) then
-- delete the old log file before opening a new one
log_sys.file.close()
fs.delete(log_sys.path)
logger.file.close()
fs.delete(logger.path)
-- re-init logger and pass dmesg_out so that it doesn't change
log.init(log_sys.path, log_sys.mode, log_sys.dmesg_out)
log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out)
-- leave a message
log_sys.file.writeLine(time_stamp .. "recycled log file")
log_sys.file.writeLine(stamped)
log_sys.file.flush()
logger.file.writeLine(time_stamp .. "recycled log file")
logger.file.writeLine(stamped)
logger.file.flush()
end
end
@ -78,33 +76,35 @@ end
-- initialize logger
---@param path string file path
---@param write_mode MODE
---@param write_mode MODE file write mode
---@param include_debug boolean whether or not to include debug logs
---@param dmesg_redirect? table terminal/window to direct dmesg to
function log.init(path, write_mode, dmesg_redirect)
log_sys.path = path
log_sys.mode = write_mode
function log.init(path, write_mode, include_debug, dmesg_redirect)
logger.path = path
logger.mode = write_mode
logger.debug = include_debug
if log_sys.mode == MODE.APPEND then
log_sys.file = fs.open(path, "a")
if logger.mode == MODE.APPEND then
logger.file = fs.open(path, "a")
else
log_sys.file = fs.open(path, "w")
logger.file = fs.open(path, "w")
end
if dmesg_redirect then
log_sys.dmesg_out = dmesg_redirect
logger.dmesg_out = dmesg_redirect
else
log_sys.dmesg_out = term.current()
logger.dmesg_out = term.current()
end
end
-- close the log file handle
function log.close()
log_sys.file.close()
logger.file.close()
end
-- direct dmesg output to a monitor/window
---@param window table window or terminal reference
function log.direct_dmesg(window) log_sys.dmesg_out = window end
function log.direct_dmesg(window) logger.dmesg_out = window end
-- dmesg style logging for boot because I like linux-y things
---@param msg string message
@ -120,7 +120,7 @@ function log.dmesg(msg, tag, tag_color)
tag = util.strval(tag)
local t_stamp = string.format("%12.2f", os.clock())
local out = log_sys.dmesg_out
local out = logger.dmesg_out
if out ~= nil then
local out_w, out_h = out.getSize()
@ -216,7 +216,7 @@ end
function log.dmesg_working(msg, tag, tag_color)
local ts_coord = log.dmesg(msg, tag, tag_color)
local out = log_sys.dmesg_out
local out = logger.dmesg_out
local width = (ts_coord.x2 - ts_coord.x1) + 1
if out ~= nil then
@ -275,7 +275,7 @@ end
---@param msg string message
---@param trace? boolean include file trace
function log.debug(msg, trace)
if LOG_DEBUG then
if logger.debug then
local dbg_info = ""
if trace then

View File

@ -39,6 +39,10 @@ function types.new_radiation_reading(r, u) return { radiation = r, unit = u } en
---@return radiation_reading
function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end
---@class coordinate_2d
---@field x integer
---@field y integer
---@class coordinate
---@field x integer
---@field y integer

View File

@ -113,10 +113,13 @@ end
---@return table lines
function util.strwrap(str, limit) return cc_strings.wrap(str, limit) end
-- luacheck: no unused args
-- concatenation with built-in to string
---@nodiscard
---@vararg any
---@return string
---@diagnostic disable-next-line: unused-vararg
function util.concat(...)
local str = ""
for _, v in ipairs(arg) do str = str .. util.strval(v) end
@ -130,10 +133,13 @@ util.c = util.concat
---@nodiscard
---@param format string
---@vararg any
---@diagnostic disable-next-line: unused-vararg
function util.sprintf(format, ...)
return string.format(format, table.unpack(arg))
end
-- luacheck: unused args
-- format a number string with commas as the thousands separator<br>
-- subtracts from spaces at the start if present for each comma used
---@nodiscard

View File

@ -28,5 +28,7 @@ config.LOG_PATH = "/log.txt"
-- 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
return config

View File

@ -119,26 +119,31 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
if unit_advert.reactor > 0 then
local target_unit = self.fac_units[unit_advert.reactor] ---@type reactor_unit
-- unit RTUs
if u_type == RTU_UNIT_TYPE.REDSTONE then
-- redstone
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then target_unit.add_redstone(unit) end
elseif u_type == RTU_UNIT_TYPE.BOILER_VALVE then
-- boiler (Mekanism 10.1+)
-- boiler
unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then target_unit.add_boiler(unit) end
elseif u_type == RTU_UNIT_TYPE.TURBINE_VALVE then
-- turbine (Mekanism 10.1+)
-- turbine
unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then target_unit.add_turbine(unit) end
elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then
-- environment detector
unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then target_unit.add_envd(unit) end
elseif u_type == RTU_UNIT_TYPE.VIRTUAL then
-- skip virtual units
log.debug(util.c(log_header, "skipping virtual RTU unit #", i))
else
log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-specific RTU type ", type_string))
end
else
-- facility RTUs
if u_type == RTU_UNIT_TYPE.REDSTONE then
-- redstone
unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q)
@ -157,6 +162,9 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
-- environment detector
unit = svrs_envd.new(id, i, unit_advert, self.modbus_q)
if type(unit) ~= "nil" then facility.add_envd(unit) end
elseif u_type == RTU_UNIT_TYPE.VIRTUAL then
-- skip virtual units
log.debug(util.c(log_header, "skipping virtual RTU unit #", i))
else
log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-independent RTU type ", type_string))
end
@ -165,7 +173,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
if unit ~= nil then
table.insert(self.units, unit)
else
elseif u_type ~= RTU_UNIT_TYPE.VIRTUAL then
_reset_config()
log.error(util.c(log_header, "bad advertisement: error occured while creating a unit (type is ", type_string, ")"))
break

View File

@ -120,9 +120,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
local io_f = {
---@nodiscard
read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end,
---@param active boolean
---@diagnostic disable-next-line: unused-local
write = function (active) end
write = function () end
}
self.db.io[port] = io_f
@ -155,9 +153,7 @@ function redstone.new(session_id, unit_id, advert, out_queue)
---@nodiscard
---@return integer
read = function () return self.phy_io.analog_in[port].phy end,
---@param value integer
---@diagnostic disable-next-line: unused-local
write = function (value) end
write = function () end
}
self.db.io[port] = io_f

View File

@ -166,6 +166,8 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
-- PUBLIC TEMPLATE FUNCTIONS --
-- luacheck: no unused args
-- handle a packet
---@param m_pkt modbus_frame
---@diagnostic disable-next-line: unused-local
@ -180,6 +182,8 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t
log.debug("template unit_session.update() called", true)
end
-- luacheck: unused args
-- invalidate build cache
function public.invalidate_cache()
log.debug("template unit_session.invalidate_cache() called", true)

View File

@ -45,6 +45,7 @@ cfv.assert_type_int(config.NUM_REACTORS)
cfv.assert_type_table(config.REACTOR_COOLING)
cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE)
cfv.assert_type_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -66,7 +67,7 @@ end
-- log init
----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE)
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG)
log.info("========================================")
log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION)

View File

@ -16,7 +16,7 @@ local println = function (str) end
-- supervisory controller communications
---@nodiscard
---@param version string supervisor version
---@param _version string supervisor version
---@param num_reactors integer number of reactors
---@param cooling_conf table cooling configuration table
---@param modem table modem device
@ -24,7 +24,7 @@ local println = function (str) end
---@param svctl_listen integer listening port for supervisor access
---@param range integer trusted device connection range
---@diagnostic disable-next-line: unused-local
function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range)
function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range)
local self = {
last_est_acks = {}
}

View File

@ -63,6 +63,7 @@ mbt.test_error__check_request(MODBUS_EXCODE.NEG_ACKNOWLEDGE)
println("PASS")
print("99 {1,2}: ")
---@diagnostic disable-next-line: param-type-mismatch
mbt.pkt_set(99, {1, 2})
mbt.test_error__check_request(MODBUS_EXCODE.ILLEGAL_FUNCTION)
println("PASS")