Compare commits
138 Commits
v1.4.2-bet
...
v1.7.2-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56377ef595 | ||
|
|
95c300e450 | ||
|
|
2985898b7e | ||
|
|
57d50e6745 | ||
|
|
3dc1a06969 | ||
|
|
fcba935240 | ||
|
|
9b32bb4675 | ||
|
|
f59f484e7b | ||
|
|
0fe9b391d8 | ||
|
|
97f0191875 | ||
|
|
70db8d782c | ||
|
|
2acd166c3e | ||
|
|
c78f7e173a | ||
|
|
99a0b0a55a | ||
|
|
6e51e70b62 | ||
|
|
fd2abad5cf | ||
|
|
b93c6b7c6e | ||
|
|
8b3f558f68 | ||
|
|
8c5289867c | ||
|
|
d179920565 | ||
|
|
504ee0594f | ||
|
|
a92f182156 | ||
|
|
c5d38a5584 | ||
|
|
9bf07e6c3e | ||
|
|
7656936982 | ||
|
|
f477ad9426 | ||
|
|
59950e9d15 | ||
|
|
11d86d92eb | ||
|
|
1275f61113 | ||
|
|
d17e2b8321 | ||
|
|
ce780c3d72 | ||
|
|
76ab4e17bf | ||
|
|
ac1733c46e | ||
|
|
17731de61b | ||
|
|
d85385c1fe | ||
|
|
e0809f52a6 | ||
|
|
b2c55f9d4b | ||
|
|
ba896ea163 | ||
|
|
1a64591256 | ||
|
|
9ce75eb4bd | ||
|
|
451f804f87 | ||
|
|
724d13510d | ||
|
|
3f01ce7ec5 | ||
|
|
df67795239 | ||
|
|
775d4dc95b | ||
|
|
b3c7263bc4 | ||
|
|
9f8732830c | ||
|
|
1c87ef18a1 | ||
|
|
f111b711c5 | ||
|
|
92d1945bea | ||
|
|
4192ea426c | ||
|
|
7bd8f34773 | ||
|
|
bdbb3071b3 | ||
|
|
def02a94d2 | ||
|
|
681bb0963e | ||
|
|
8f7d7c3ead | ||
|
|
c0f45cfb8b | ||
|
|
455653074a | ||
|
|
1202289fab | ||
|
|
acb7b5b4cb | ||
|
|
9bd79dacad | ||
|
|
c544d140bf | ||
|
|
353cb3622b | ||
|
|
b54f15bad6 | ||
|
|
4d9783beca | ||
|
|
5529774b0e | ||
|
|
2a541ef3fe | ||
|
|
e1b4d72ef8 | ||
|
|
6a0992c7a4 | ||
|
|
cff7c724be | ||
|
|
47bda73afe | ||
|
|
8daedc109c | ||
|
|
a164c18a50 | ||
|
|
4d663ada8d | ||
|
|
084a153a79 | ||
|
|
4ed6ec1c63 | ||
|
|
d3c2ba7bee | ||
|
|
55ff9dad4b | ||
|
|
0d6022f5e3 | ||
|
|
8b136d78a8 | ||
|
|
a5214730ef | ||
|
|
9f3ad3caf0 | ||
|
|
9bb2a99be5 | ||
|
|
65ace26258 | ||
|
|
61d975d13f | ||
|
|
1d7d6e9817 | ||
|
|
a2e0999cea | ||
|
|
1edee7f64b | ||
|
|
df61ec2c62 | ||
|
|
bf7a316b04 | ||
|
|
96c4444184 | ||
|
|
59eac62c33 | ||
|
|
ab193db153 | ||
|
|
7d65bba589 | ||
|
|
dcef5a96f0 | ||
|
|
ba0900ac65 | ||
|
|
8f54e95519 | ||
|
|
7b9824b6f9 | ||
|
|
b6835fc7d1 | ||
|
|
bc5a94cd3b | ||
|
|
2a3d868402 | ||
|
|
b998634da1 | ||
|
|
5225380523 | ||
|
|
0e7ea7102c | ||
|
|
8924ba4e99 | ||
|
|
a8071db08e | ||
|
|
fb3c7ded06 | ||
|
|
f6b0a49904 | ||
|
|
bfbbfb164b | ||
|
|
57763702ff | ||
|
|
f469754bb7 | ||
|
|
336662de62 | ||
|
|
9073009eb0 | ||
|
|
ffac6996ed | ||
|
|
da3c92b3bf | ||
|
|
712c7a8f3b | ||
|
|
737afe586d | ||
|
|
d69796b607 | ||
|
|
1cdf66a8c3 | ||
|
|
282c7db3eb | ||
|
|
a02529b9f7 | ||
|
|
af38025f50 | ||
|
|
b28e4d1e95 | ||
|
|
75dfa3ae73 | ||
|
|
4a3455fa60 | ||
|
|
a2fa6570dc | ||
|
|
aef8281ad6 | ||
|
|
d42327a20d | ||
|
|
49db75f34d | ||
|
|
bc87030491 | ||
|
|
9266d7d8e1 | ||
|
|
ef5567ad46 | ||
|
|
302f3d913f | ||
|
|
650b9c1811 | ||
|
|
543ac8c9fe | ||
|
|
7f19f76c0b | ||
|
|
8d76c86309 | ||
|
|
a4be6a6dde |
4
.github/workflows/manifest.yml
vendored
4
.github/workflows/manifest.yml
vendored
@@ -1,5 +1,5 @@
|
||||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy Installation Manifests and Versions
|
||||
# Deploy installation manifests and shields versions
|
||||
name: Deploy Installation Data
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
_notes/
|
||||
program.sh
|
||||
/*program.sh
|
||||
58
README.md
58
README.md
@@ -7,6 +7,29 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi
|
||||

|
||||

|
||||
|
||||
### [Join](https://discord.gg/R9NSCkhcwt) the Discord!
|
||||
|
||||

|
||||
|
||||
## Released Component Versions
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
Mod Requirements:
|
||||
- CC: Tweaked
|
||||
- Mekanism v10.1+
|
||||
@@ -16,32 +39,11 @@ Mod Recommendations:
|
||||
|
||||
v10.1+ is required due the complete support of CC:Tweaked added in Mekanism v10.1
|
||||
|
||||
There was also an apparent bug with boilers disconnecting and reconnecting when active in my test world on 10.0.24, so it may not even have been an option to fully implement this with support for 10.0.
|
||||
|
||||
## Released Component Versions
|
||||
|
||||
### Core
|
||||
|
||||

|
||||

|
||||
|
||||
### Utilities
|
||||
|
||||

|
||||
|
||||
### Applications
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
You can install this on a ComputerCraft computer using either:
|
||||
* `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua`
|
||||
* `pastebin get eRz6cUNM ccmsi.lua`
|
||||
* `pastebin get sqUN6VUb ccmsi.lua`
|
||||
|
||||
## [SCADA](https://en.wikipedia.org/wiki/SCADA)
|
||||
> Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.
|
||||
@@ -86,14 +88,8 @@ A vaguely-modbus [modbus](https://en.wikipedia.org/wiki/Modbus) communication pr
|
||||
- Input Registers: Multi-Byte Read-Only (analog inputs)
|
||||
- Holding Registers: Multi-Byte Read/Write (analog I/O)
|
||||
|
||||
### Security and Encryption
|
||||
### Security
|
||||
|
||||
TBD, I am planning on AES symmetric encryption for security + HMAC to prevent replay attacks. This will be done utilizing this codebase: https://github.com/somesocks/lua-lockbox.
|
||||
HMAC message authentication is available as a configuration option to prevent replay attacks and generally prevent control or false data reporting within a system's network. This is done utilizing the [lua-lockbox](https://github.com/somesocks/lua-lockbox) project.
|
||||
|
||||
This is somewhat important here as otherwise anyone can just control your setup, which is undeseriable. Unlike normal Minecraft PVP chaos, it would be very difficult to identify who is messing with your system, as with an Ender Modem they can do it from effectively anywhere and the server operators would have to check every computer's filesystem to find suspicious code.
|
||||
|
||||
The other security mitigation for commanding (no effect on monitoring) is to enforce a maximum authorized transmission range, which has been added as a configurable feature.
|
||||
|
||||
## Known Issues
|
||||
|
||||
None yet since the switch to requiring 10.1+!
|
||||
The other, simpler security feature is to enforce a maximum authorized transmission range, which is also a configurable feature on each device.
|
||||
|
||||
712
ccmsi.lua
712
ccmsi.lua
@@ -1,8 +1,6 @@
|
||||
--
|
||||
-- ComputerCraft Mekanism SCADA System Installer Utility
|
||||
--
|
||||
|
||||
--[[
|
||||
CC-MEK-SCADA Installer Utility
|
||||
|
||||
Copyright (c) 2023 Mikayla Fischler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
@@ -20,30 +18,97 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
local function println(message) print(tostring(message)) end
|
||||
local function print(message) term.write(tostring(message)) end
|
||||
|
||||
local CCMSI_VERSION = "v1.2"
|
||||
local CCMSI_VERSION = "v1.9a"
|
||||
|
||||
local install_dir = "/.install-cache"
|
||||
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
||||
local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/"
|
||||
|
||||
local opts = { ... }
|
||||
local mode = nil
|
||||
local app = nil
|
||||
local mode, app, target
|
||||
local install_manifest = manifest_path .. "main/install_manifest.json"
|
||||
|
||||
local function red() term.setTextColor(colors.red) end
|
||||
local function orange() term.setTextColor(colors.orange) end
|
||||
local function yellow() term.setTextColor(colors.yellow) end
|
||||
local function green() term.setTextColor(colors.green) end
|
||||
local function blue() term.setTextColor(colors.blue) end
|
||||
local function white() term.setTextColor(colors.white) end
|
||||
local function lgray() term.setTextColor(colors.lightGray) end
|
||||
|
||||
-- get command line option in list
|
||||
local function get_opt(opt, options)
|
||||
for _, v in pairs(options) do if opt == v then return v end end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- wait for any key to be pressed
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local function any_key() os.pullEvent("key_up") end
|
||||
|
||||
-- ask the user yes or no
|
||||
local function ask_y_n(question, default)
|
||||
print(question)
|
||||
if default == true then print(" (Y/n)? ") else print(" (y/N)? ") end
|
||||
local response = read();any_key()
|
||||
if response == "" then return default
|
||||
elseif response == "Y" or response == "y" then return true
|
||||
elseif response == "N" or response == "n" then return false
|
||||
else return nil end
|
||||
end
|
||||
|
||||
-- print out a white + blue text message
|
||||
local function pkg_message(message, package) white();print(message .. " ");blue();println(package);white() end
|
||||
|
||||
-- indicate actions to be taken based on package differences for installs/updates
|
||||
local function show_pkg_change(name, v)
|
||||
if v.v_local ~= nil then
|
||||
if v.v_local ~= v.v_remote then
|
||||
print("[" .. name .. "] updating ");blue();print(v.v_local);white();print(" \xbb ");blue();println(v.v_remote);white()
|
||||
elseif mode == "install" then
|
||||
pkg_message("[" .. name .. "] reinstalling", v.v_local)
|
||||
end
|
||||
else pkg_message("[" .. name .. "] new install of", v.v_remote) end
|
||||
return v.v_local ~= v.v_remote
|
||||
end
|
||||
|
||||
-- read the local manifest file
|
||||
local function read_local_manifest()
|
||||
local local_ok = false
|
||||
local local_manifest = {}
|
||||
local imfile = fs.open("install_manifest.json", "r")
|
||||
if imfile ~= nil then
|
||||
local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end)
|
||||
imfile.close()
|
||||
end
|
||||
return local_ok, local_manifest
|
||||
end
|
||||
|
||||
-- get the manifest from GitHub
|
||||
local function get_remote_manifest()
|
||||
local response, error = http.get(install_manifest)
|
||||
if response == nil then
|
||||
orange();println("Failed to get installation manifest from GitHub, cannot update or install.")
|
||||
red();println("HTTP error: " .. error);white()
|
||||
return false, {}
|
||||
end
|
||||
|
||||
local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end)
|
||||
if not ok then red();println("error parsing remote installation manifest");white() end
|
||||
|
||||
return ok, manifest
|
||||
end
|
||||
|
||||
-- record the local installation manifest
|
||||
---@param manifest table
|
||||
---@param dependencies table
|
||||
local function write_install_manifest(manifest, dependencies)
|
||||
local versions = {}
|
||||
for key, value in pairs(manifest.versions) do
|
||||
local is_dependency = false
|
||||
for _, dependency in pairs(dependencies) do
|
||||
if key == "bootloader" and dependency == "system" then
|
||||
is_dependency = true
|
||||
break
|
||||
if (key == "bootloader" and dependency == "system") or key == dependency then
|
||||
is_dependency = true;break
|
||||
end
|
||||
end
|
||||
|
||||
if key == app or key == "comms" or is_dependency then versions[key] = value end
|
||||
end
|
||||
|
||||
@@ -54,116 +119,141 @@ local function write_install_manifest(manifest, dependencies)
|
||||
imfile.close()
|
||||
end
|
||||
|
||||
--
|
||||
-- recursively build a tree out of the file manifest
|
||||
local function gen_tree(manifest)
|
||||
local function _tree_add(tree, split)
|
||||
if #split > 1 then
|
||||
local name = table.remove(split, 1)
|
||||
if tree[name] == nil then tree[name] = {} end
|
||||
table.insert(tree[name], _tree_add(tree[name], split))
|
||||
else return split[1] end
|
||||
return nil
|
||||
end
|
||||
|
||||
local list, tree = {}, {}
|
||||
|
||||
-- make a list of each and every file
|
||||
for _, files in pairs(manifest.files) do for i = 1, #files do table.insert(list, files[i]) end end
|
||||
|
||||
for i = 1, #list do
|
||||
local split = {}
|
||||
string.gsub(list[i], "([^/]+)", function(c) split[#split + 1] = c end)
|
||||
if #split == 1 then table.insert(tree, list[i])
|
||||
else table.insert(tree, _tree_add(tree, split)) end
|
||||
end
|
||||
|
||||
return tree
|
||||
end
|
||||
|
||||
local function _in_array(val, array)
|
||||
for _, v in pairs(array) do if v == val then return true end end
|
||||
return false
|
||||
end
|
||||
|
||||
local function _clean_dir(dir, tree)
|
||||
if tree == nil then tree = {} end
|
||||
local ls = fs.list(dir)
|
||||
for _, val in pairs(ls) do
|
||||
local path = dir .. "/" .. val
|
||||
if fs.isDir(path) then
|
||||
_clean_dir(path, tree[val])
|
||||
if #fs.list(path) == 0 then fs.delete(path);println("deleted " .. path) end
|
||||
elseif not _in_array(val, tree) then
|
||||
fs.delete(path)
|
||||
println("deleted " .. path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- go through app/common directories to delete unused files
|
||||
local function clean(manifest)
|
||||
local root_ext = false
|
||||
local tree = gen_tree(manifest)
|
||||
|
||||
table.insert(tree, "install_manifest.json")
|
||||
table.insert(tree, "ccmsi.lua")
|
||||
table.insert(tree, "log.txt")
|
||||
|
||||
lgray()
|
||||
|
||||
local ls = fs.list("/")
|
||||
for _, val in pairs(ls) do
|
||||
if fs.isDir(val) then
|
||||
if tree[val] ~= nil then _clean_dir("/" .. val, tree[val]) end
|
||||
if #fs.list(val) == 0 then fs.delete(val);println("deleted " .. val) end
|
||||
elseif not _in_array(val, tree) then
|
||||
root_ext = true
|
||||
yellow();println(val .. " not used")
|
||||
end
|
||||
end
|
||||
|
||||
white()
|
||||
if root_ext then println("Files in root directory won't be automatically deleted.") end
|
||||
end
|
||||
|
||||
-- get and validate command line options
|
||||
--
|
||||
|
||||
println("-- CC Mekanism SCADA Installer " .. CCMSI_VERSION .. " --")
|
||||
|
||||
if #opts == 0 or opts[1] == "help" then
|
||||
println("usage: ccmsi <mode> <app> <tag/branch>")
|
||||
println("usage: ccmsi <mode> <app> <branch>")
|
||||
println("<mode>")
|
||||
term.setTextColor(colors.lightGray)
|
||||
lgray()
|
||||
println(" check - check latest versions avilable")
|
||||
term.setTextColor(colors.yellow)
|
||||
println(" ccmsi check <tag/branch> for target")
|
||||
term.setTextColor(colors.lightGray)
|
||||
yellow()
|
||||
println(" ccmsi check <branch> for target")
|
||||
lgray()
|
||||
println(" install - fresh install, overwrites config")
|
||||
println(" update - update files EXCEPT for config/logs")
|
||||
println(" remove - delete files EXCEPT for config/logs")
|
||||
println(" purge - delete files INCLUDING config/logs")
|
||||
term.setTextColor(colors.white)
|
||||
println("<app>")
|
||||
term.setTextColor(colors.lightGray)
|
||||
white();println("<app>");lgray()
|
||||
println(" reactor-plc - reactor PLC firmware")
|
||||
println(" rtu - RTU firmware")
|
||||
println(" supervisor - supervisor server application")
|
||||
println(" coordinator - coordinator application")
|
||||
println(" pocket - pocket application")
|
||||
term.setTextColor(colors.white)
|
||||
println("<tag/branch>")
|
||||
term.setTextColor(colors.yellow)
|
||||
println(" second parameter when used with check")
|
||||
term.setTextColor(colors.lightGray)
|
||||
println(" note: defaults to main")
|
||||
println(" target GitHub tag or branch name")
|
||||
println(" installer - ccmsi installer (update only)")
|
||||
white();println("<branch>")
|
||||
lgray();println(" main (default) | latest | devel");white()
|
||||
return
|
||||
else
|
||||
for _, v in pairs({ "check", "install", "update", "remove", "purge" }) do
|
||||
if opts[1] == v then
|
||||
mode = v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
mode = get_opt(opts[1], { "check", "install", "update", "remove", "purge" })
|
||||
if mode == nil then
|
||||
println("unrecognized mode")
|
||||
red();println("Unrecognized mode.");white()
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in pairs({ "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" }) do
|
||||
if opts[2] == v then
|
||||
app = v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
app = get_opt(opts[2], { "reactor-plc", "rtu", "supervisor", "coordinator", "pocket", "installer" })
|
||||
if app == nil and mode ~= "check" then
|
||||
println("unrecognized application")
|
||||
red();println("Unrecognized application.");white()
|
||||
return
|
||||
elseif app == "installer" and mode ~= "update" then
|
||||
red();println("Installer app only supports 'update' option.");white()
|
||||
return
|
||||
end
|
||||
|
||||
-- determine target
|
||||
if mode == "check" then target = opts[2] else target = opts[3] end
|
||||
if (target ~= "main") and (target ~= "latest") and (target ~= "devel") then
|
||||
if (target and target ~= "") then yellow();println("Unknown target, defaulting to 'main'");white() end
|
||||
target = "main"
|
||||
end
|
||||
|
||||
-- set paths
|
||||
install_manifest = manifest_path .. target .. "/install_manifest.json"
|
||||
repo_path = repo_path .. target .. "/"
|
||||
end
|
||||
|
||||
--
|
||||
-- run selected mode
|
||||
--
|
||||
|
||||
if mode == "check" then
|
||||
-------------------------
|
||||
-- GET REMOTE MANIFEST --
|
||||
-------------------------
|
||||
|
||||
if opts[2] then manifest_path = manifest_path .. opts[2] .. "/" else manifest_path = manifest_path .. "main/" end
|
||||
local install_manifest = manifest_path .. "install_manifest.json"
|
||||
|
||||
local response, error = http.get(install_manifest)
|
||||
|
||||
if response == nil then
|
||||
term.setTextColor(colors.orange)
|
||||
println("failed to get installation manifest from GitHub, cannot update or install")
|
||||
term.setTextColor(colors.red)
|
||||
println("HTTP error: " .. error)
|
||||
term.setTextColor(colors.white)
|
||||
return
|
||||
end
|
||||
|
||||
local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end)
|
||||
|
||||
if not ok then
|
||||
term.setTextColor(colors.red)
|
||||
println("error parsing remote installation manifest")
|
||||
term.setTextColor(colors.white)
|
||||
return
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- GET LOCAL MANIFEST --
|
||||
------------------------
|
||||
|
||||
local imfile = fs.open("install_manifest.json", "r")
|
||||
local local_ok = false
|
||||
local local_manifest = {}
|
||||
|
||||
if imfile ~= nil then
|
||||
local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end)
|
||||
imfile.close()
|
||||
end
|
||||
local ok, manifest = get_remote_manifest()
|
||||
if not ok then return end
|
||||
|
||||
local local_ok, local_manifest = read_local_manifest()
|
||||
if not local_ok then
|
||||
term.setTextColor(colors.yellow)
|
||||
println("failed to load local installation information")
|
||||
term.setTextColor(colors.white)
|
||||
|
||||
yellow();println("failed to load local installation information");white()
|
||||
local_manifest = { versions = { installer = CCMSI_VERSION } }
|
||||
else
|
||||
local_manifest.versions.installer = CCMSI_VERSION
|
||||
@@ -174,190 +264,107 @@ if mode == "check" then
|
||||
term.setTextColor(colors.purple)
|
||||
print(string.format("%-14s", "[" .. key .. "]"))
|
||||
if key == "installer" or (local_ok and (local_manifest.versions[key] ~= nil)) then
|
||||
term.setTextColor(colors.blue)
|
||||
print(local_manifest.versions[key])
|
||||
blue();print(local_manifest.versions[key])
|
||||
if value ~= local_manifest.versions[key] then
|
||||
term.setTextColor(colors.white)
|
||||
print(" (")
|
||||
white();print(" (")
|
||||
term.setTextColor(colors.cyan)
|
||||
print(value)
|
||||
term.setTextColor(colors.white)
|
||||
println(" available)")
|
||||
else
|
||||
term.setTextColor(colors.green)
|
||||
println(" (up to date)")
|
||||
end
|
||||
print(value);white();println(" available)")
|
||||
else green();println(" (up to date)") end
|
||||
else
|
||||
term.setTextColor(colors.lightGray)
|
||||
print("not installed")
|
||||
term.setTextColor(colors.white)
|
||||
print(" (latest ")
|
||||
lgray();print("not installed");white();print(" (latest ")
|
||||
term.setTextColor(colors.cyan)
|
||||
print(value)
|
||||
term.setTextColor(colors.white)
|
||||
println(")")
|
||||
print(value);white();println(")")
|
||||
end
|
||||
end
|
||||
|
||||
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()
|
||||
end
|
||||
elseif mode == "install" or mode == "update" then
|
||||
-------------------------
|
||||
-- GET REMOTE MANIFEST --
|
||||
-------------------------
|
||||
local update_installer = app == "installer"
|
||||
local ok, manifest = get_remote_manifest()
|
||||
if not ok then return end
|
||||
|
||||
if opts[3] then repo_path = repo_path .. opts[3] .. "/" else repo_path = repo_path .. "main/" end
|
||||
if opts[3] then manifest_path = manifest_path .. opts[3] .. "/" else manifest_path = manifest_path .. "main/" end
|
||||
local install_manifest = manifest_path .. "install_manifest.json"
|
||||
local ver = {
|
||||
app = { v_local = nil, v_remote = nil, changed = false },
|
||||
boot = { v_local = nil, v_remote = nil, changed = false },
|
||||
comms = { v_local = nil, v_remote = nil, changed = false },
|
||||
common = { v_local = nil, v_remote = nil, changed = false },
|
||||
graphics = { v_local = nil, v_remote = nil, changed = false },
|
||||
lockbox = { v_local = nil, v_remote = nil, changed = false }
|
||||
}
|
||||
|
||||
local response, error = http.get(install_manifest)
|
||||
-- try to find local versions
|
||||
local local_ok, lmnf = read_local_manifest()
|
||||
if not local_ok then
|
||||
if mode == "update" then
|
||||
red();println("Failed to load local installation information, cannot update.");white()
|
||||
return
|
||||
end
|
||||
elseif not update_installer then
|
||||
ver.boot.v_local = lmnf.versions.bootloader
|
||||
ver.app.v_local = lmnf.versions[app]
|
||||
ver.comms.v_local = lmnf.versions.comms
|
||||
ver.common.v_local = lmnf.versions.common
|
||||
ver.graphics.v_local = lmnf.versions.graphics
|
||||
ver.lockbox.v_local = lmnf.versions.lockbox
|
||||
|
||||
if response == nil then
|
||||
term.setTextColor(colors.orange)
|
||||
println("failed to get installation manifest from GitHub, cannot update or install")
|
||||
term.setTextColor(colors.red)
|
||||
println("HTTP error: " .. error)
|
||||
term.setTextColor(colors.white)
|
||||
if lmnf.versions[app] == nil then
|
||||
red();println("Another application is already installed, please purge it before installing a new application.");white()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
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 update_installer or ask_y_n("Would you like to update now") then
|
||||
lgray();println("GET ccmsi.lua")
|
||||
local dl, err = http.get(repo_path .. "ccmsi.lua")
|
||||
|
||||
if dl == nil then
|
||||
red();println("HTTP Error " .. err)
|
||||
println("Installer download failed.");white()
|
||||
else
|
||||
local handle = fs.open(debug.getinfo(1, "S").source:sub(2), "w") -- this file, regardless of name or location
|
||||
handle.write(dl.readAll())
|
||||
handle.close()
|
||||
green();println("Installer updated successfully.");white()
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
elseif update_installer then
|
||||
green();println("Installer already up-to-date.");white()
|
||||
return
|
||||
end
|
||||
|
||||
local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end)
|
||||
ver.boot.v_remote = manifest.versions.bootloader
|
||||
ver.app.v_remote = manifest.versions[app]
|
||||
ver.comms.v_remote = manifest.versions.comms
|
||||
ver.common.v_remote = manifest.versions.common
|
||||
ver.graphics.v_remote = manifest.versions.graphics
|
||||
ver.lockbox.v_remote = manifest.versions.lockbox
|
||||
|
||||
if not ok then
|
||||
term.setTextColor(colors.red)
|
||||
println("error parsing remote installation manifest")
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- GET LOCAL MANIFEST --
|
||||
------------------------
|
||||
|
||||
local imfile = fs.open("install_manifest.json", "r")
|
||||
local local_ok = false
|
||||
local local_manifest = {}
|
||||
|
||||
if imfile ~= nil then
|
||||
local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end)
|
||||
imfile.close()
|
||||
end
|
||||
|
||||
local local_app_version = nil
|
||||
local local_comms_version = nil
|
||||
local local_boot_version = nil
|
||||
|
||||
-- try to find local versions
|
||||
if not local_ok then
|
||||
if mode == "update" then
|
||||
term.setTextColor(colors.red)
|
||||
println("failed to load local installation information, cannot update")
|
||||
term.setTextColor(colors.white)
|
||||
return
|
||||
end
|
||||
else
|
||||
local_app_version = local_manifest.versions[app]
|
||||
local_comms_version = local_manifest.versions.comms
|
||||
local_boot_version = local_manifest.versions.bootloader
|
||||
|
||||
if local_manifest.versions[app] == nil then
|
||||
term.setTextColor(colors.red)
|
||||
println("another application is already installed, please purge it before installing a new application")
|
||||
term.setTextColor(colors.white)
|
||||
return
|
||||
end
|
||||
|
||||
local_manifest.versions.installer = CCMSI_VERSION
|
||||
if manifest.versions.installer ~= CCMSI_VERSION then
|
||||
term.setTextColor(colors.yellow)
|
||||
println("a newer version of the installer is available, consider downloading it")
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
end
|
||||
|
||||
local remote_app_version = manifest.versions[app]
|
||||
local remote_comms_version = manifest.versions.comms
|
||||
local remote_boot_version = manifest.versions.bootloader
|
||||
|
||||
term.setTextColor(colors.green)
|
||||
green()
|
||||
if mode == "install" then
|
||||
println("installing " .. app .. " files...")
|
||||
println("Installing " .. app .. " files...")
|
||||
elseif mode == "update" then
|
||||
println("updating " .. app .. " files... (keeping old config.lua)")
|
||||
println("Updating " .. app .. " files... (keeping old config.lua)")
|
||||
end
|
||||
term.setTextColor(colors.white)
|
||||
white()
|
||||
|
||||
-- display bootloader version change information
|
||||
if local_boot_version ~= nil then
|
||||
if local_boot_version ~= remote_boot_version then
|
||||
print("[bootldr] updating ")
|
||||
term.setTextColor(colors.blue)
|
||||
print(local_boot_version)
|
||||
term.setTextColor(colors.white)
|
||||
print(" \xbb ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(remote_boot_version)
|
||||
term.setTextColor(colors.white)
|
||||
elseif mode == "install" then
|
||||
print("[bootldr] reinstalling ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(local_boot_version)
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
else
|
||||
print("[bootldr] new install of ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(remote_boot_version)
|
||||
term.setTextColor(colors.white)
|
||||
ver.boot.changed = show_pkg_change("bootldr", ver.boot)
|
||||
ver.common.changed = show_pkg_change("common", ver.common)
|
||||
ver.comms.changed = show_pkg_change("comms", ver.comms)
|
||||
if ver.comms.changed and ver.comms.v_local ~= nil then
|
||||
print("[comms] ");yellow();println("other devices on the network will require an update");white()
|
||||
end
|
||||
ver.app.changed = show_pkg_change(app, ver.app)
|
||||
ver.graphics.changed = show_pkg_change("graphics", ver.graphics)
|
||||
ver.lockbox.changed = show_pkg_change("lockbox", ver.lockbox)
|
||||
|
||||
-- display app version change information
|
||||
if local_app_version ~= nil then
|
||||
if local_app_version ~= remote_app_version then
|
||||
print("[" .. app .. "] updating ")
|
||||
term.setTextColor(colors.blue)
|
||||
print(local_app_version)
|
||||
term.setTextColor(colors.white)
|
||||
print(" \xbb ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(remote_app_version)
|
||||
term.setTextColor(colors.white)
|
||||
elseif mode == "install" then
|
||||
print("[" .. app .. "] reinstalling ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(local_app_version)
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
else
|
||||
print("[" .. app .. "] new install of ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(remote_app_version)
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
|
||||
-- display comms version change information
|
||||
if local_comms_version ~= nil then
|
||||
if local_comms_version ~= remote_comms_version then
|
||||
print("[comms] updating ")
|
||||
term.setTextColor(colors.blue)
|
||||
print(local_comms_version)
|
||||
term.setTextColor(colors.white)
|
||||
print(" \xbb ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(remote_comms_version)
|
||||
term.setTextColor(colors.white)
|
||||
print("[comms] ")
|
||||
term.setTextColor(colors.yellow)
|
||||
println("other devices on the network will require an update")
|
||||
term.setTextColor(colors.white)
|
||||
elseif mode == "install" then
|
||||
print("[comms] reinstalling ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(local_comms_version)
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
else
|
||||
print("[comms] new install of ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(remote_comms_version)
|
||||
term.setTextColor(colors.white)
|
||||
end
|
||||
-- ask for confirmation
|
||||
if not ask_y_n("Continue", false) then return end
|
||||
|
||||
--------------------------
|
||||
-- START INSTALL/UPDATE --
|
||||
@@ -382,54 +389,45 @@ elseif mode == "install" or mode == "update" then
|
||||
-- check space constraints
|
||||
if space_available < space_required then
|
||||
single_file_mode = true
|
||||
term.setTextColor(colors.yellow)
|
||||
println("WARNING: Insufficient space available for a full download!")
|
||||
term.setTextColor(colors.white)
|
||||
yellow();println("WARNING: Insufficient space available for a full download!");white()
|
||||
println("Files can be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.")
|
||||
println("Do you wish to continue? (y/N)")
|
||||
|
||||
local confirm = read()
|
||||
if confirm ~= "y" and confirm ~= "Y" then
|
||||
println("installation cancelled")
|
||||
if mode == "update" then println("If installation still fails, delete this device's log file or uninstall the app (not purge) and try again.") end
|
||||
if not ask_y_n("Do you wish to continue", false) then
|
||||
println("Operation cancelled.")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
os.sleep(2)
|
||||
|
||||
local success = true
|
||||
|
||||
-- helper function to check if a dependency is unchanged
|
||||
local function unchanged(dependency)
|
||||
if dependency == "system" then return not ver.boot.changed
|
||||
elseif dependency == "graphics" then return not ver.graphics.changed
|
||||
elseif dependency == "lockbox" then return not ver.lockbox.changed
|
||||
elseif dependency == "common" then return not (ver.common.changed or ver.comms.changed)
|
||||
elseif dependency == app then return not ver.app.changed
|
||||
else return true end
|
||||
end
|
||||
|
||||
if not single_file_mode then
|
||||
if fs.exists(install_dir) then
|
||||
fs.delete(install_dir)
|
||||
fs.makeDir(install_dir)
|
||||
end
|
||||
if fs.exists(install_dir) then fs.delete(install_dir);fs.makeDir(install_dir) end
|
||||
|
||||
-- download all dependencies
|
||||
for _, dependency in pairs(dependencies) do
|
||||
if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then
|
||||
-- skip system package if unchanged, skip app package if not changed
|
||||
-- skip packages that have no version if app version didn't change
|
||||
term.setTextColor(colors.white)
|
||||
print("skipping download of unchanged package ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(dependency)
|
||||
if mode == "update" and unchanged(dependency) then
|
||||
pkg_message("skipping download of unchanged package", dependency)
|
||||
else
|
||||
term.setTextColor(colors.white)
|
||||
print("downloading package ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(dependency)
|
||||
pkg_message("downloading package", dependency)
|
||||
lgray()
|
||||
|
||||
term.setTextColor(colors.lightGray)
|
||||
local files = file_list[dependency]
|
||||
for _, file in pairs(files) do
|
||||
println("GET " .. file)
|
||||
local dl, err = http.get(repo_path .. file)
|
||||
|
||||
if dl == nil then
|
||||
term.setTextColor(colors.red)
|
||||
println("GET HTTP Error " .. err)
|
||||
red();println("HTTP Error " .. err)
|
||||
success = false
|
||||
break
|
||||
else
|
||||
@@ -444,20 +442,12 @@ elseif mode == "install" or mode == "update" then
|
||||
-- copy in downloaded files (installation)
|
||||
if success then
|
||||
for _, dependency in pairs(dependencies) do
|
||||
if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then
|
||||
-- skip system package if unchanged, skip app package if not changed
|
||||
-- skip packages that have no version if app version didn't change
|
||||
term.setTextColor(colors.white)
|
||||
print("skipping install of unchanged package ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(dependency)
|
||||
if mode == "update" and unchanged(dependency) then
|
||||
pkg_message("skipping install of unchanged package", dependency)
|
||||
else
|
||||
term.setTextColor(colors.white)
|
||||
print("installing package ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(dependency)
|
||||
pkg_message("installing package", dependency)
|
||||
lgray()
|
||||
|
||||
term.setTextColor(colors.lightGray)
|
||||
local files = file_list[dependency]
|
||||
for _, file in pairs(files) do
|
||||
if mode == "install" or file ~= config_file then
|
||||
@@ -473,41 +463,28 @@ elseif mode == "install" or mode == "update" then
|
||||
fs.delete(install_dir)
|
||||
|
||||
if success then
|
||||
-- if we made it here, then none of the file system functions threw exceptions
|
||||
-- that means everything is OK
|
||||
write_install_manifest(manifest, dependencies)
|
||||
term.setTextColor(colors.green)
|
||||
green()
|
||||
if mode == "install" then
|
||||
println("installation completed successfully")
|
||||
else
|
||||
println("update completed successfully")
|
||||
end
|
||||
println("Installation completed successfully.")
|
||||
else println("Update completed successfully.") end
|
||||
white();println("Ready to clean up unused files, press any key to continue...")
|
||||
any_key();clean(manifest)
|
||||
white();println("Done.")
|
||||
else
|
||||
if mode == "install" then
|
||||
term.setTextColor(colors.red)
|
||||
println("installation failed")
|
||||
else
|
||||
term.setTextColor(colors.orange)
|
||||
println("update failed, existing files unmodified")
|
||||
end
|
||||
red();println("Installation failed.")
|
||||
else orange();println("Update failed, existing files unmodified.") end
|
||||
end
|
||||
else
|
||||
-- go through all files and replace one by one
|
||||
for _, dependency in pairs(dependencies) do
|
||||
if mode == "update" and ((dependency == "system" and local_boot_version == remote_boot_version) or (local_app_version == remote_app_version)) then
|
||||
-- skip system package if unchanged, skip app package if not changed
|
||||
-- skip packages that have no version if app version didn't change
|
||||
term.setTextColor(colors.white)
|
||||
print("skipping install of unchanged package ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(dependency)
|
||||
if mode == "update" and unchanged(dependency) then
|
||||
pkg_message("skipping install of unchanged package", dependency)
|
||||
else
|
||||
term.setTextColor(colors.white)
|
||||
print("installing package ")
|
||||
term.setTextColor(colors.blue)
|
||||
println(dependency)
|
||||
pkg_message("installing package", dependency)
|
||||
lgray()
|
||||
|
||||
term.setTextColor(colors.lightGray)
|
||||
local files = file_list[dependency]
|
||||
for _, file in pairs(files) do
|
||||
if mode == "install" or file ~= config_file then
|
||||
@@ -515,7 +492,7 @@ elseif mode == "install" or mode == "update" then
|
||||
local dl, err = http.get(repo_path .. file)
|
||||
|
||||
if dl == nil then
|
||||
println("GET HTTP Error " .. err)
|
||||
red();println("HTTP Error " .. err)
|
||||
success = false
|
||||
break
|
||||
else
|
||||
@@ -529,55 +506,43 @@ elseif mode == "install" or mode == "update" then
|
||||
end
|
||||
|
||||
if success then
|
||||
-- if we made it here, then none of the file system functions threw exceptions
|
||||
-- that means everything is OK
|
||||
write_install_manifest(manifest, dependencies)
|
||||
term.setTextColor(colors.green)
|
||||
green()
|
||||
if mode == "install" then
|
||||
println("installation completed successfully")
|
||||
else
|
||||
println("update completed successfully")
|
||||
end
|
||||
println("Installation completed successfully.")
|
||||
else println("Update completed successfully.") end
|
||||
white();println("Ready to clean up unused files, press any key to continue...")
|
||||
any_key();clean(manifest)
|
||||
white();println("Done.")
|
||||
else
|
||||
term.setTextColor(colors.red)
|
||||
red()
|
||||
if mode == "install" then
|
||||
println("installation failed, files may have been skipped")
|
||||
else
|
||||
println("update failed, files may have been skipped")
|
||||
end
|
||||
println("Installation failed, files may have been skipped.")
|
||||
else println("Update failed, files may have been skipped.") end
|
||||
end
|
||||
end
|
||||
elseif mode == "remove" or mode == "purge" then
|
||||
local imfile = fs.open("install_manifest.json", "r")
|
||||
local ok = false
|
||||
local manifest = {}
|
||||
|
||||
if imfile ~= nil then
|
||||
ok, manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end)
|
||||
imfile.close()
|
||||
end
|
||||
|
||||
local ok, manifest = read_local_manifest()
|
||||
if not ok then
|
||||
term.setTextColor(colors.red)
|
||||
println("error parsing local installation manifest")
|
||||
term.setTextColor(colors.white)
|
||||
red();println("Error parsing local installation manifest.");white()
|
||||
return
|
||||
elseif mode == "remove" and manifest.versions[app] == nil then
|
||||
term.setTextColor(colors.red)
|
||||
println(app .. " is not installed")
|
||||
term.setTextColor(colors.white)
|
||||
red();println(app .. " is not installed, cannot remove.");white()
|
||||
return
|
||||
end
|
||||
|
||||
term.setTextColor(colors.orange)
|
||||
orange()
|
||||
if mode == "remove" then
|
||||
println("removing all " .. app .. " files except for config.lua and log.txt...")
|
||||
println("Removing all " .. app .. " files except for config and log...")
|
||||
elseif mode == "purge" then
|
||||
println("purging all " .. app .. " files...")
|
||||
println("Purging all " .. app .. " files including config and log...")
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
os.sleep(2)
|
||||
-- ask for confirmation
|
||||
if not ask_y_n("Continue", false) then return end
|
||||
|
||||
-- delete unused files first
|
||||
clean(manifest)
|
||||
|
||||
local file_list = manifest.files
|
||||
local dependencies = manifest.depends[app]
|
||||
@@ -585,9 +550,8 @@ elseif mode == "remove" or mode == "purge" then
|
||||
|
||||
table.insert(dependencies, app)
|
||||
|
||||
term.setTextColor(colors.lightGray)
|
||||
|
||||
-- delete log file if purging
|
||||
lgray()
|
||||
if mode == "purge" and fs.exists(config_file) then
|
||||
local log_deleted = pcall(function ()
|
||||
local config = require(app .. ".config")
|
||||
@@ -598,11 +562,9 @@ elseif mode == "remove" or mode == "purge" then
|
||||
end)
|
||||
|
||||
if not log_deleted then
|
||||
term.setTextColor(colors.red)
|
||||
println("failed to delete log file")
|
||||
term.setTextColor(colors.lightGray)
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
os.sleep(1)
|
||||
red();println("Failed to delete log file.")
|
||||
white();println("press any key to continue...")
|
||||
any_key();lgray()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -611,10 +573,7 @@ elseif mode == "remove" or mode == "purge" then
|
||||
local files = file_list[dependency]
|
||||
for _, file in pairs(files) do
|
||||
if mode == "purge" or file ~= config_file then
|
||||
if fs.exists(file) then
|
||||
fs.delete(file)
|
||||
println("deleted " .. file)
|
||||
end
|
||||
if fs.exists(file) then fs.delete(file);println("deleted " .. file) end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -623,11 +582,7 @@ elseif mode == "remove" or mode == "purge" then
|
||||
local folder = files[1]
|
||||
while true do
|
||||
local dir = fs.getDir(folder)
|
||||
if dir == "" or dir == ".." then
|
||||
break
|
||||
else
|
||||
folder = dir
|
||||
end
|
||||
if dir == "" or dir == ".." then break else folder = dir end
|
||||
end
|
||||
|
||||
if fs.isDir(folder) then
|
||||
@@ -635,19 +590,15 @@ elseif mode == "remove" or mode == "purge" then
|
||||
println("deleted directory " .. folder)
|
||||
end
|
||||
elseif dependency == app then
|
||||
-- delete individual subdirectories so we can leave the config
|
||||
for _, folder in pairs(files) do
|
||||
while true do
|
||||
local dir = fs.getDir(folder)
|
||||
if dir == "" or dir == ".." or dir == app then
|
||||
break
|
||||
else
|
||||
folder = dir
|
||||
end
|
||||
if dir == "" or dir == ".." or dir == app then break else folder = dir end
|
||||
end
|
||||
|
||||
if folder ~= app and fs.isDir(folder) then
|
||||
fs.delete(folder)
|
||||
println("deleted app subdirectory " .. folder)
|
||||
fs.delete(folder);println("deleted app subdirectory " .. folder)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -660,13 +611,12 @@ elseif mode == "remove" or mode == "purge" then
|
||||
else
|
||||
-- remove all data from versions list to show nothing is installed
|
||||
manifest.versions = {}
|
||||
imfile = fs.open("install_manifest.json", "w")
|
||||
local imfile = fs.open("install_manifest.json", "w")
|
||||
imfile.write(textutils.serializeJSON(manifest))
|
||||
imfile.close()
|
||||
end
|
||||
|
||||
term.setTextColor(colors.green)
|
||||
println("done!")
|
||||
green();println("Done!")
|
||||
end
|
||||
|
||||
term.setTextColor(colors.white)
|
||||
white()
|
||||
|
||||
@@ -11,6 +11,10 @@ config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
config.SV_TIMEOUT = 5
|
||||
config.API_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"
|
||||
|
||||
-- expected number of reactor units, used only to require that number of unit monitors
|
||||
config.NUM_UNITS = 4
|
||||
@@ -22,6 +26,9 @@ config.SOUNDER_VOLUME = 1.0
|
||||
-- true for 24 hour time on main view screen
|
||||
config.TIME_24_HOUR = true
|
||||
|
||||
-- disable flow view (for legacy layouts)
|
||||
config.DISABLE_FLOW_VIEW = false
|
||||
|
||||
-- log path
|
||||
config.LOG_PATH = "/log.txt"
|
||||
-- log mode
|
||||
|
||||
@@ -2,6 +2,7 @@ local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local util = require("scada-common.util")
|
||||
local types = require("scada-common.types")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local process = require("coordinator.process")
|
||||
@@ -12,7 +13,6 @@ local dialog = require("coordinator.ui.dialog")
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
local PROTOCOL = comms.PROTOCOL
|
||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||
@@ -22,6 +22,8 @@ local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE
|
||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||
local FAC_COMMAND = comms.FAC_COMMAND
|
||||
|
||||
local LINK_TIMEOUT = 60.0
|
||||
|
||||
local coordinator = {}
|
||||
|
||||
-- request the user to select a monitor
|
||||
@@ -47,12 +49,15 @@ end
|
||||
|
||||
-- configure monitor layout
|
||||
---@param num_units integer number of units expected
|
||||
---@param disable_flow_view boolean disable flow view (legacy)
|
||||
---@return boolean success, monitors_struct? monitors
|
||||
function coordinator.configure_monitors(num_units)
|
||||
function coordinator.configure_monitors(num_units, disable_flow_view)
|
||||
---@class monitors_struct
|
||||
local monitors = {
|
||||
primary = nil,
|
||||
primary_name = "",
|
||||
flow = nil,
|
||||
flow_name = "",
|
||||
unit_displays = {},
|
||||
unit_name_map = {}
|
||||
}
|
||||
@@ -67,8 +72,8 @@ function coordinator.configure_monitors(num_units)
|
||||
table.insert(available, iface)
|
||||
end
|
||||
|
||||
-- we need a certain number of monitors (1 per unit + 1 primary display)
|
||||
local num_displays_needed = num_units + 1
|
||||
-- we need a certain number of monitors (1 per unit + 1 primary display + 1 flow display)
|
||||
local num_displays_needed = num_units + util.trinary(disable_flow_view, 1, 2)
|
||||
if #names < num_displays_needed then
|
||||
local message = "not enough monitors connected (need " .. num_displays_needed .. ")"
|
||||
println(message)
|
||||
@@ -81,10 +86,12 @@ function coordinator.configure_monitors(num_units)
|
||||
log.warning("configure_monitors(): failed to load coordinator settings file (may not exist yet)")
|
||||
else
|
||||
local _primary = settings.get("PRIMARY_DISPLAY")
|
||||
local _flow = settings.get("FLOW_DISPLAY")
|
||||
local _unitd = settings.get("UNIT_DISPLAYS")
|
||||
|
||||
-- filter out already assigned monitors
|
||||
util.filter_table(available, function (x) return x ~= _primary end)
|
||||
util.filter_table(available, function (x) return x ~= _flow end)
|
||||
if type(_unitd) == "table" then
|
||||
util.filter_table(available, function (x) return not util.table_contains(_unitd, x) end)
|
||||
end
|
||||
@@ -104,7 +111,6 @@ function coordinator.configure_monitors(num_units)
|
||||
end
|
||||
|
||||
while iface_primary_display == nil and #available > 0 do
|
||||
-- lets get a monitor
|
||||
iface_primary_display = ask_monitor(available)
|
||||
end
|
||||
|
||||
@@ -116,6 +122,33 @@ function coordinator.configure_monitors(num_units)
|
||||
monitors.primary = ppm.get_periph(iface_primary_display)
|
||||
monitors.primary_name = iface_primary_display
|
||||
|
||||
--------------------------
|
||||
-- FLOW MONITOR DISPLAY --
|
||||
--------------------------
|
||||
|
||||
if not disable_flow_view then
|
||||
local iface_flow_display = settings.get("FLOW_DISPLAY") ---@type boolean|string|nil
|
||||
|
||||
if not util.table_contains(names, iface_flow_display) then
|
||||
println("flow monitor display is not connected")
|
||||
local response = dialog.ask_y_n("would you like to change it", true)
|
||||
if response == false then return false end
|
||||
iface_flow_display = nil
|
||||
end
|
||||
|
||||
while iface_flow_display == nil and #available > 0 do
|
||||
iface_flow_display = ask_monitor(available)
|
||||
end
|
||||
|
||||
if type(iface_flow_display) ~= "string" then return false end
|
||||
|
||||
settings.set("FLOW_DISPLAY", iface_flow_display)
|
||||
util.filter_table(available, function (x) return x ~= iface_flow_display end)
|
||||
|
||||
monitors.flow = ppm.get_periph(iface_flow_display)
|
||||
monitors.flow_name = iface_flow_display
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- UNIT DISPLAYS --
|
||||
-------------------
|
||||
@@ -128,7 +161,6 @@ function coordinator.configure_monitors(num_units)
|
||||
local display = nil
|
||||
|
||||
while display == nil and #available > 0 do
|
||||
-- lets get a monitor
|
||||
println("please select monitor for unit #" .. i)
|
||||
display = ask_monitor(available)
|
||||
end
|
||||
@@ -150,7 +182,6 @@ function coordinator.configure_monitors(num_units)
|
||||
end
|
||||
|
||||
while display == nil and #available > 0 do
|
||||
-- lets get a monitor
|
||||
display = ask_monitor(available)
|
||||
end
|
||||
|
||||
@@ -183,7 +214,8 @@ local function log_dmesg(message, dmesg_tag, working)
|
||||
GRAPHICS = colors.green,
|
||||
SYSTEM = colors.cyan,
|
||||
BOOT = colors.blue,
|
||||
COMMS = colors.purple
|
||||
COMMS = colors.purple,
|
||||
CRYPTO = colors.yellow
|
||||
}
|
||||
|
||||
if working then
|
||||
@@ -197,6 +229,7 @@ function coordinator.log_graphics(message) log_dmesg(message, "GRAPHICS") end
|
||||
function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end
|
||||
function coordinator.log_boot(message) log_dmesg(message, "BOOT") end
|
||||
function coordinator.log_comms(message) log_dmesg(message, "COMMS") end
|
||||
function coordinator.log_crypto(message) log_dmesg(message, "CRYPTO") end
|
||||
|
||||
-- log a message for communications connecting, providing access to progress indication control functions
|
||||
---@nodiscard
|
||||
@@ -212,38 +245,38 @@ end
|
||||
-- coordinator communications
|
||||
---@nodiscard
|
||||
---@param version string coordinator version
|
||||
---@param modem table modem device
|
||||
---@param nic nic network interface device
|
||||
---@param num_units integer number of configured units for number of monitors, checked against SV
|
||||
---@param crd_channel integer port of configured supervisor
|
||||
---@param svr_channel integer listening port for supervisor replys
|
||||
---@param pkt_channel integer listening port for pocket API
|
||||
---@param range integer trusted device connection range
|
||||
---@param sv_watchdog watchdog
|
||||
function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel, range, sv_watchdog)
|
||||
function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pkt_channel, range, sv_watchdog)
|
||||
local self = {
|
||||
sv_linked = false,
|
||||
sv_addr = comms.BROADCAST,
|
||||
sv_seq_num = 0,
|
||||
sv_r_seq_num = nil,
|
||||
sv_config_err = false,
|
||||
connected = false,
|
||||
last_est_ack = ESTABLISH_ACK.ALLOW,
|
||||
last_api_est_acks = {}
|
||||
last_api_est_acks = {},
|
||||
est_start = 0,
|
||||
est_last = 0,
|
||||
est_tick_waiting = nil,
|
||||
est_task_done = nil
|
||||
}
|
||||
|
||||
comms.set_trusted_range(range)
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(crd_channel)
|
||||
end
|
||||
-- configure network channels
|
||||
nic.closeAll()
|
||||
nic.open(crd_channel)
|
||||
|
||||
_conf_channels()
|
||||
|
||||
-- link modem to apisessions
|
||||
apisessions.init(modem)
|
||||
-- link nic to apisessions
|
||||
apisessions.init(nic)
|
||||
|
||||
-- send a packet to the supervisor
|
||||
---@param msg_type SCADA_MGMT_TYPE|SCADA_CRDN_TYPE
|
||||
@@ -263,7 +296,7 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv_addr, self.sv_seq_num, protocol, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(svr_channel, crd_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, crd_channel, s_pkt)
|
||||
self.sv_seq_num = self.sv_seq_num + 1
|
||||
end
|
||||
|
||||
@@ -277,7 +310,7 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, { ack })
|
||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(pkt_channel, crd_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(pkt_channel, crd_channel, s_pkt)
|
||||
self.last_api_est_acks[packet.src_addr()] = ack
|
||||
end
|
||||
|
||||
@@ -297,12 +330,61 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
---@class coord_comms
|
||||
local public = {}
|
||||
|
||||
-- reconnect a newly connected modem
|
||||
---@param new_modem table
|
||||
function public.reconnect_modem(new_modem)
|
||||
modem = new_modem
|
||||
apisessions.relink_modem(new_modem)
|
||||
_conf_channels()
|
||||
-- try to connect to the supervisor if not already linked
|
||||
---@param abort boolean? true to print out cancel info if not linked (use on program terminate)
|
||||
---@return boolean ok, boolean start_ui
|
||||
function public.try_connect(abort)
|
||||
local ok = true
|
||||
local start_ui = false
|
||||
|
||||
if not self.sv_linked then
|
||||
if self.est_tick_waiting == nil then
|
||||
self.est_start = util.time_s()
|
||||
self.est_last = self.est_start
|
||||
|
||||
self.est_tick_waiting, self.est_task_done =
|
||||
coordinator.log_comms_connecting("attempting to connect to configured supervisor on channel " .. svr_channel)
|
||||
|
||||
_send_establish()
|
||||
else
|
||||
self.est_tick_waiting(math.max(0, LINK_TIMEOUT - (util.time_s() - self.est_start)))
|
||||
end
|
||||
|
||||
if abort or (util.time_s() - self.est_start) >= LINK_TIMEOUT then
|
||||
self.est_task_done(false)
|
||||
|
||||
if abort then
|
||||
coordinator.log_comms("supervisor connection attempt cancelled by user")
|
||||
elseif self.sv_config_err then
|
||||
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
|
||||
elseif not self.sv_linked then
|
||||
if self.last_est_ack == ESTABLISH_ACK.DENY then
|
||||
coordinator.log_comms("supervisor connection attempt denied")
|
||||
elseif self.last_est_ack == ESTABLISH_ACK.COLLISION then
|
||||
coordinator.log_comms("supervisor connection failed due to collision")
|
||||
elseif self.last_est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
coordinator.log_comms("supervisor connection failed due to version mismatch")
|
||||
else
|
||||
coordinator.log_comms("supervisor connection failed with no valid response")
|
||||
end
|
||||
end
|
||||
|
||||
ok = false
|
||||
elseif self.sv_config_err then
|
||||
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
|
||||
ok = false
|
||||
elseif (util.time_s() - self.est_last) > 1.0 then
|
||||
_send_establish()
|
||||
self.est_last = util.time_s()
|
||||
end
|
||||
elseif self.est_tick_waiting ~= nil then
|
||||
self.est_task_done(true)
|
||||
self.est_tick_waiting = nil
|
||||
self.est_task_done = nil
|
||||
start_ui = true
|
||||
end
|
||||
|
||||
return ok, start_ui
|
||||
end
|
||||
|
||||
-- close the connection to the server
|
||||
@@ -311,71 +393,15 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.sv_linked = false
|
||||
self.sv_r_seq_num = nil
|
||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
_send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.CLOSE, {})
|
||||
end
|
||||
|
||||
-- attempt to connect to the subervisor
|
||||
---@nodiscard
|
||||
---@param timeout_s number timeout in seconds
|
||||
---@param tick_dmesg_waiting function callback to tick dmesg waiting
|
||||
---@param task_done function callback to show done on dmesg
|
||||
---@return boolean sv_linked true if connected, false otherwise
|
||||
--- EVENT_CONSUMER: this function consumes events
|
||||
function public.sv_connect(timeout_s, tick_dmesg_waiting, task_done)
|
||||
local clock = util.new_clock(1)
|
||||
local start = util.time_s()
|
||||
local terminated = false
|
||||
|
||||
_send_establish()
|
||||
|
||||
clock.start()
|
||||
|
||||
while (util.time_s() - start) < timeout_s and (not self.sv_linked) and (not self.sv_config_err) do
|
||||
local event, p1, p2, p3, p4, p5 = util.pull_event()
|
||||
|
||||
if event == "timer" and clock.is_clock(p1) then
|
||||
-- timed out attempt, try again
|
||||
tick_dmesg_waiting(math.max(0, timeout_s - (util.time_s() - start)))
|
||||
_send_establish()
|
||||
clock.start()
|
||||
elseif event == "timer" then
|
||||
-- keep checking watchdog timers
|
||||
apisessions.check_all_watchdogs(p1)
|
||||
elseif event == "modem_message" then
|
||||
-- handle message
|
||||
local packet = public.parse_packet(p1, p2, p3, p4, p5)
|
||||
public.handle_packet(packet)
|
||||
elseif event == "terminate" then
|
||||
terminated = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
task_done(self.sv_linked)
|
||||
|
||||
if terminated then
|
||||
coordinator.log_comms("supervisor connection attempt cancelled by user")
|
||||
elseif self.sv_config_err then
|
||||
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
|
||||
elseif not self.sv_linked then
|
||||
if self.last_est_ack == ESTABLISH_ACK.DENY then
|
||||
coordinator.log_comms("supervisor connection attempt denied")
|
||||
elseif self.last_est_ack == ESTABLISH_ACK.COLLISION then
|
||||
coordinator.log_comms("supervisor connection failed due to collision")
|
||||
elseif self.last_est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
coordinator.log_comms("supervisor connection failed due to version mismatch")
|
||||
else
|
||||
coordinator.log_comms("supervisor connection failed with no valid response")
|
||||
end
|
||||
end
|
||||
|
||||
return self.sv_linked
|
||||
end
|
||||
|
||||
-- send a facility command
|
||||
---@param cmd FAC_COMMAND command
|
||||
function public.send_fac_command(cmd)
|
||||
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { cmd })
|
||||
---@param option any? optional option options for the optional options (like waste mode)
|
||||
function public.send_fac_command(cmd, option)
|
||||
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { cmd, option })
|
||||
end
|
||||
|
||||
-- send the auto process control configuration with a start command
|
||||
@@ -389,7 +415,7 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
-- send a unit command
|
||||
---@param cmd UNIT_COMMAND command
|
||||
---@param unit integer unit ID
|
||||
---@param option any? optional option options for the optional options (like burn rate) (does option still look like a word?)
|
||||
---@param option any? optional option options for the optional options (like burn rate)
|
||||
function public.send_unit_command(cmd, unit, option)
|
||||
_send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_CMD, { cmd, unit, option })
|
||||
end
|
||||
@@ -402,13 +428,10 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
---@param distance integer
|
||||
---@return mgmt_frame|crdn_frame|capi_frame|nil packet
|
||||
function public.parse_packet(side, sender, reply_to, message, distance)
|
||||
local s_pkt = nic.receive(side, sender, reply_to, message, distance)
|
||||
local pkt = nil
|
||||
local s_pkt = comms.scada_packet()
|
||||
|
||||
-- parse packet as generic SCADA packet
|
||||
s_pkt.receive(side, sender, reply_to, message, distance)
|
||||
|
||||
if s_pkt.is_valid() then
|
||||
if s_pkt then
|
||||
-- get as SCADA management packet
|
||||
if s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||
local mgmt_pkt = comms.mgmt_packet()
|
||||
@@ -437,7 +460,10 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
|
||||
-- handle a packet
|
||||
---@param packet mgmt_frame|crdn_frame|capi_frame|nil
|
||||
---@return boolean close_ui
|
||||
function public.handle_packet(packet)
|
||||
local was_linked = self.sv_linked
|
||||
|
||||
if packet ~= nil then
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local r_chan = packet.scada_frame.remote_channel()
|
||||
@@ -447,7 +473,9 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
if l_chan ~= crd_channel then
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
elseif r_chan == pkt_channel then
|
||||
if protocol == PROTOCOL.COORD_API then
|
||||
if not self.sv_linked then
|
||||
log.debug("discarding pocket API packet before linked to supervisor")
|
||||
elseif protocol == PROTOCOL.COORD_API then
|
||||
---@cast packet capi_frame
|
||||
-- look for an associated session
|
||||
local session = apisessions.find_session(src_addr)
|
||||
@@ -486,7 +514,6 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
elseif dev_type == DEVICE_TYPE.PKT then
|
||||
-- pocket linking request
|
||||
local id = apisessions.establish_session(src_addr, firmware_v)
|
||||
println(util.c("[API] pocket (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
||||
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
@@ -509,12 +536,12 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
-- check sequence number
|
||||
if self.sv_r_seq_num == nil then
|
||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif self.connected and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
elseif self.sv_linked and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
return false
|
||||
elseif self.sv_linked and src_addr ~= self.sv_addr then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
|
||||
return
|
||||
return false
|
||||
else
|
||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
||||
end
|
||||
@@ -576,6 +603,10 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
end
|
||||
elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then
|
||||
iocontrol.get_db().facility.ack_alarms_ack(ack)
|
||||
elseif cmd == FAC_COMMAND.SET_WASTE_MODE then
|
||||
process.waste_ack_handle(packet.data[2])
|
||||
elseif cmd == FAC_COMMAND.SET_PU_FB then
|
||||
process.pu_fb_ack_handle(packet.data[2])
|
||||
else
|
||||
log.debug(util.c("received facility command ack with unknown command ", cmd))
|
||||
end
|
||||
@@ -640,70 +671,7 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- connection with supervisor established
|
||||
if packet.length == 2 then
|
||||
local est_ack = packet.data[1]
|
||||
local config = packet.data[2]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
if type(config) == "table" and #config > 1 then
|
||||
-- get configuration
|
||||
|
||||
---@class facility_conf
|
||||
local conf = {
|
||||
num_units = config[1], ---@type integer
|
||||
defs = {} -- boilers and turbines
|
||||
}
|
||||
|
||||
if (#config - 1) == (conf.num_units * 2) then
|
||||
-- record sequence of pairs of [#boilers, #turbines] per unit
|
||||
for i = 2, #config do
|
||||
table.insert(conf.defs, config[i])
|
||||
end
|
||||
|
||||
-- init io controller
|
||||
iocontrol.init(conf, public)
|
||||
|
||||
self.sv_addr = src_addr
|
||||
self.sv_linked = true
|
||||
self.sv_config_err = false
|
||||
else
|
||||
self.sv_config_err = true
|
||||
log.warning("invalid supervisor configuration definitions received, establish failed")
|
||||
end
|
||||
else
|
||||
log.debug("invalid supervisor configuration table received, establish failed")
|
||||
end
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported")
|
||||
end
|
||||
|
||||
self.last_est_ack = est_ack
|
||||
elseif packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
if self.last_est_ack ~= est_ack then
|
||||
log.info("supervisor connection denied")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
if self.last_est_ack ~= est_ack then
|
||||
log.warning("supervisor connection denied due to collision")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.last_est_ack ~= est_ack then
|
||||
log.warning("supervisor comms version mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet reply (len = 1) unsupported")
|
||||
end
|
||||
|
||||
self.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
elseif self.sv_linked then
|
||||
if self.sv_linked then
|
||||
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||
-- keep alive request received, echo back
|
||||
if packet.length == 1 then
|
||||
@@ -728,11 +696,78 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.sv_linked = false
|
||||
self.sv_r_seq_num = nil
|
||||
println_ts("server connection closed by remote host")
|
||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
log.info("server connection closed by remote host")
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type)
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- connection with supervisor established
|
||||
if packet.length == 2 then
|
||||
local est_ack = packet.data[1]
|
||||
local config = packet.data[2]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
-- reset to disconnected before validating
|
||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
|
||||
if type(config) == "table" and #config == 2 then
|
||||
-- get configuration
|
||||
|
||||
---@class facility_conf
|
||||
local conf = {
|
||||
num_units = config[1], ---@type integer
|
||||
cooling = config[2] ---@type sv_cooling_conf
|
||||
}
|
||||
|
||||
if conf.num_units == num_units then
|
||||
-- init io controller
|
||||
iocontrol.init(conf, public)
|
||||
|
||||
self.sv_addr = src_addr
|
||||
self.sv_linked = true
|
||||
self.sv_r_seq_num = nil
|
||||
self.sv_config_err = false
|
||||
|
||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED)
|
||||
else
|
||||
self.sv_config_err = true
|
||||
log.warning("supervisor config's number of units don't match coordinator's config, establish failed")
|
||||
end
|
||||
else
|
||||
log.debug("invalid supervisor configuration table received, establish failed")
|
||||
end
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported")
|
||||
end
|
||||
|
||||
self.last_est_ack = est_ack
|
||||
elseif packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
if self.last_est_ack ~= est_ack then
|
||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DENIED)
|
||||
log.info("supervisor connection denied")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
if self.last_est_ack ~= est_ack then
|
||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.COLLISION)
|
||||
log.warning("supervisor connection denied due to collision")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.last_est_ack ~= est_ack then
|
||||
iocontrol.fp_link_state(types.PANEL_LINK_STATE.BAD_VERSION)
|
||||
log.warning("supervisor comms version mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet reply (len = 1) unsupported")
|
||||
end
|
||||
|
||||
self.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("discarding non-link SCADA_MGMT packet before linked")
|
||||
end
|
||||
@@ -743,6 +778,8 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
log.debug("received packet for unknown channel " .. r_chan, true)
|
||||
end
|
||||
end
|
||||
|
||||
return was_linked and not self.sv_linked
|
||||
end
|
||||
|
||||
-- check if the coordinator is still linked to the supervisor
|
||||
|
||||
@@ -10,9 +10,15 @@ local util = require("scada-common.util")
|
||||
local process = require("coordinator.process")
|
||||
local sounder = require("coordinator.sounder")
|
||||
|
||||
local pgi = require("coordinator.ui.pgi")
|
||||
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
local PROCESS = types.PROCESS
|
||||
|
||||
-- nominal RTT is ping (0ms to 10ms usually) + 500ms for CRD main loop tick
|
||||
local WARN_RTT = 1000 -- 2x as long as expected w/ 0 ping
|
||||
local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
|
||||
|
||||
local iocontrol = {}
|
||||
|
||||
---@class ioctl
|
||||
@@ -27,13 +33,27 @@ local function __generic_ack(success) end
|
||||
|
||||
-- luacheck: unused args
|
||||
|
||||
-- initialize front panel PSIL
|
||||
---@param firmware_v string coordinator version
|
||||
---@param comms_v string comms version
|
||||
function iocontrol.init_fp(firmware_v, comms_v)
|
||||
---@class ioctl_front_panel
|
||||
io.fp = { ps = psil.create() }
|
||||
|
||||
io.fp.ps.publish("version", firmware_v)
|
||||
io.fp.ps.publish("comms_version", comms_v)
|
||||
end
|
||||
|
||||
-- initialize the coordinator IO controller
|
||||
---@param conf facility_conf configuration
|
||||
---@param comms coord_comms comms reference
|
||||
function iocontrol.init(conf, comms)
|
||||
-- facility data structure
|
||||
---@class ioctl_facility
|
||||
io.facility = {
|
||||
num_units = conf.num_units, ---@type integer
|
||||
num_units = conf.num_units,
|
||||
tank_mode = conf.cooling.fac_tank_mode,
|
||||
tank_defs = conf.cooling.fac_tank_defs,
|
||||
all_sys_ok = false,
|
||||
rtu_count = 0,
|
||||
|
||||
@@ -52,6 +72,10 @@ function iocontrol.init(conf, comms)
|
||||
gen_fault = false
|
||||
},
|
||||
|
||||
---@type WASTE_PRODUCT
|
||||
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||
auto_pu_fallback_active = false,
|
||||
|
||||
radiation = types.new_zero_radiation_reading(),
|
||||
|
||||
save_cfg_ack = __generic_ack,
|
||||
@@ -60,22 +84,124 @@ function iocontrol.init(conf, comms)
|
||||
scram_ack = __generic_ack,
|
||||
ack_alarms_ack = __generic_ack,
|
||||
|
||||
alarm_tones = { false, false, false, false, false, false, false, false },
|
||||
|
||||
ps = psil.create(),
|
||||
|
||||
induction_ps_tbl = {},
|
||||
induction_data_tbl = {},
|
||||
|
||||
sps_ps_tbl = {},
|
||||
sps_data_tbl = {},
|
||||
|
||||
tank_ps_tbl = {},
|
||||
tank_data_tbl = {},
|
||||
|
||||
env_d_ps = psil.create(),
|
||||
env_d_data = {}
|
||||
}
|
||||
|
||||
-- create induction tables (currently only 1 is supported)
|
||||
for _ = 1, conf.num_units do
|
||||
local data = {} ---@type imatrix_session_db
|
||||
table.insert(io.facility.induction_ps_tbl, psil.create())
|
||||
table.insert(io.facility.induction_data_tbl, data)
|
||||
-- create induction and SPS tables (currently only 1 of each is supported)
|
||||
table.insert(io.facility.induction_ps_tbl, psil.create())
|
||||
table.insert(io.facility.induction_data_tbl, {})
|
||||
table.insert(io.facility.sps_ps_tbl, psil.create())
|
||||
table.insert(io.facility.sps_data_tbl, {})
|
||||
|
||||
-- determine tank information
|
||||
if io.facility.tank_mode == 0 then
|
||||
io.facility.tank_defs = {}
|
||||
-- on facility tank mode 0, setup tank defs to match unit TANK option
|
||||
for i = 1, conf.num_units do
|
||||
io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TANK, 1, 0)
|
||||
end
|
||||
|
||||
io.facility.tank_list = { table.unpack(io.facility.tank_defs) }
|
||||
else
|
||||
-- decode the layout of tanks from the connections definitions
|
||||
local tank_mode = io.facility.tank_mode
|
||||
local tank_defs = io.facility.tank_defs
|
||||
local tank_list = { table.unpack(tank_defs) }
|
||||
|
||||
local function calc_fdef(start_idx, end_idx)
|
||||
local first = 4
|
||||
for i = start_idx, end_idx do
|
||||
if io.facility.tank_defs[i] == 2 then
|
||||
if i < first then first = i end
|
||||
end
|
||||
end
|
||||
return first
|
||||
end
|
||||
|
||||
if tank_mode == 1 then
|
||||
-- (1) 1 total facility tank (A A A A)
|
||||
local first_fdef = calc_fdef(1, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if i > first_fdef and tank_defs[i] == 2 then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 2 then
|
||||
-- (2) 2 total facility tanks (A A A B)
|
||||
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 3 then
|
||||
-- (3) 2 total facility tanks (A A B B)
|
||||
for _, a in pairs({ 1, 3 }) do
|
||||
local b = a + 1
|
||||
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
|
||||
tank_list[b] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 4 then
|
||||
-- (4) 2 total facility tanks (A B B B)
|
||||
local first_fdef = calc_fdef(2, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 5 then
|
||||
-- (5) 3 total facility tanks (A A B C)
|
||||
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 6 then
|
||||
-- (6) 3 total facility tanks (A B B C)
|
||||
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 7 then
|
||||
-- (7) 3 total facility tanks (A B C C)
|
||||
local first_fdef = calc_fdef(3, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
io.facility.tank_list = tank_list
|
||||
end
|
||||
|
||||
-- create facility tank tables
|
||||
for i = 1, #io.facility.tank_list do
|
||||
if io.facility.tank_list[i] == 2 then
|
||||
table.insert(io.facility.tank_ps_tbl, psil.create())
|
||||
table.insert(io.facility.tank_data_tbl, {})
|
||||
end
|
||||
end
|
||||
|
||||
-- create unit data structures
|
||||
io.units = {}
|
||||
for i = 1, conf.num_units do
|
||||
local function ack(alarm) process.ack_alarm(i, alarm) end
|
||||
@@ -87,11 +213,16 @@ function iocontrol.init(conf, comms)
|
||||
|
||||
num_boilers = 0,
|
||||
num_turbines = 0,
|
||||
num_snas = 0,
|
||||
has_tank = conf.cooling.r_cool[i].TANK,
|
||||
|
||||
control_state = false,
|
||||
burn_rate_cmd = 0.0,
|
||||
waste_control = 0,
|
||||
radiation = types.new_zero_radiation_reading(),
|
||||
sna_prod_rate = 0.0,
|
||||
|
||||
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
|
||||
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||
|
||||
-- auto control group
|
||||
a_group = 0,
|
||||
@@ -100,10 +231,10 @@ function iocontrol.init(conf, comms)
|
||||
scram = function () process.scram(i) end,
|
||||
reset_rps = function () process.reset_rps(i) end,
|
||||
ack_alarms = function () process.ack_all_alarms(i) end,
|
||||
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
||||
set_waste = function (mode) process.set_waste(i, mode) end, ---@param mode integer waste processing mode
|
||||
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
||||
set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode
|
||||
|
||||
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0
|
||||
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual
|
||||
|
||||
start_ack = __generic_ack,
|
||||
scram_ack = __generic_ack,
|
||||
@@ -152,21 +283,33 @@ function iocontrol.init(conf, comms)
|
||||
boiler_data_tbl = {},
|
||||
|
||||
turbine_ps_tbl = {},
|
||||
turbine_data_tbl = {}
|
||||
turbine_data_tbl = {},
|
||||
|
||||
tank_ps_tbl = {},
|
||||
tank_data_tbl = {}
|
||||
}
|
||||
|
||||
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
||||
if io.facility.tank_mode ~= 0 then
|
||||
entry.has_tank = conf.cooling.fac_tank_defs[i] > 0
|
||||
end
|
||||
|
||||
-- create boiler tables
|
||||
for _ = 1, conf.defs[(i * 2) - 1] do
|
||||
local data = {} ---@type boilerv_session_db
|
||||
for _ = 1, conf.cooling.r_cool[i].BOILERS do
|
||||
table.insert(entry.boiler_ps_tbl, psil.create())
|
||||
table.insert(entry.boiler_data_tbl, data)
|
||||
table.insert(entry.boiler_data_tbl, {})
|
||||
end
|
||||
|
||||
-- create turbine tables
|
||||
for _ = 1, conf.defs[i * 2] do
|
||||
local data = {} ---@type turbinev_session_db
|
||||
for _ = 1, conf.cooling.r_cool[i].TURBINES do
|
||||
table.insert(entry.turbine_ps_tbl, psil.create())
|
||||
table.insert(entry.turbine_data_tbl, data)
|
||||
table.insert(entry.turbine_data_tbl, {})
|
||||
end
|
||||
|
||||
-- create tank tables
|
||||
if io.facility.tank_defs[i] == 1 then
|
||||
table.insert(entry.tank_ps_tbl, psil.create())
|
||||
table.insert(entry.tank_data_tbl, {})
|
||||
end
|
||||
|
||||
entry.num_boilers = #entry.boiler_data_tbl
|
||||
@@ -179,6 +322,102 @@ function iocontrol.init(conf, comms)
|
||||
process.init(io, comms)
|
||||
end
|
||||
|
||||
--#region Front Panel PSIL
|
||||
|
||||
-- toggle heartbeat indicator
|
||||
function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end
|
||||
|
||||
-- report presence of the wireless modem
|
||||
---@param has_modem boolean
|
||||
function iocontrol.fp_has_modem(has_modem) io.fp.ps.publish("has_modem", has_modem) end
|
||||
|
||||
-- report presence of the speaker
|
||||
---@param has_speaker boolean
|
||||
function iocontrol.fp_has_speaker(has_speaker) io.fp.ps.publish("has_speaker", has_speaker) end
|
||||
|
||||
-- report supervisor link state
|
||||
---@param state integer
|
||||
function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) end
|
||||
|
||||
-- report monitor connection state
|
||||
---@param id string|integer unit ID for unit monitor, "main" for main monitor, or "flow" for flow monitor
|
||||
function iocontrol.fp_monitor_state(id, connected)
|
||||
local name = nil
|
||||
|
||||
if id == "main" then
|
||||
name = "main_monitor"
|
||||
elseif id == "flow" then
|
||||
name = "flow_monitor"
|
||||
elseif type(id) == "number" then
|
||||
name = "unit_monitor_" .. id
|
||||
end
|
||||
|
||||
if name ~= nil then
|
||||
io.fp.ps.publish(name, connected)
|
||||
end
|
||||
end
|
||||
|
||||
-- report PKT firmware version and PKT session connection state
|
||||
---@param session_id integer PKT session
|
||||
---@param fw string firmware version
|
||||
---@param s_addr integer PKT computer ID
|
||||
function iocontrol.fp_pkt_connected(session_id, fw, s_addr)
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_fw", fw)
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_addr", util.sprintf("@ C% 3d", s_addr))
|
||||
pgi.create_pkt_entry(session_id)
|
||||
end
|
||||
|
||||
-- report PKT session disconnected
|
||||
---@param session_id integer PKT session
|
||||
function iocontrol.fp_pkt_disconnected(session_id)
|
||||
pgi.delete_pkt_entry(session_id)
|
||||
end
|
||||
|
||||
-- transmit PKT session RTT
|
||||
---@param session_id integer PKT session
|
||||
---@param rtt integer round trip time
|
||||
function iocontrol.fp_pkt_rtt(session_id, rtt)
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > HIGH_RTT then
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > WARN_RTT then
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green)
|
||||
end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Builds
|
||||
|
||||
-- record and publish multiblock RTU build data
|
||||
---@param id integer
|
||||
---@param entry table
|
||||
---@param data_tbl table
|
||||
---@param ps_tbl table
|
||||
---@param create boolean? true to create an entry if non exists, false to fail on missing
|
||||
---@return boolean ok true if data saved, false if invalid ID
|
||||
local function _record_multiblock_build(id, entry, data_tbl, ps_tbl, create)
|
||||
local exists = type(data_tbl[id]) == "table"
|
||||
if exists or create then
|
||||
if not exists then
|
||||
ps_tbl[id] = psil.create()
|
||||
data_tbl[id] = {}
|
||||
end
|
||||
|
||||
data_tbl[id].formed = entry[1] ---@type boolean
|
||||
data_tbl[id].build = entry[2] ---@type table
|
||||
|
||||
ps_tbl[id].publish("formed", entry[1])
|
||||
|
||||
for key, val in pairs(data_tbl[id].build) do ps_tbl[id].publish(key, val) end
|
||||
end
|
||||
|
||||
return exists or (create == true)
|
||||
end
|
||||
|
||||
-- populate facility structure builds
|
||||
---@param build table
|
||||
---@return boolean valid
|
||||
@@ -191,21 +430,29 @@ function iocontrol.record_facility_builds(build)
|
||||
-- induction matricies
|
||||
if type(build.induction) == "table" then
|
||||
for id, matrix in pairs(build.induction) do
|
||||
if type(fac.induction_data_tbl[id]) == "table" then
|
||||
fac.induction_data_tbl[id].formed = matrix[1] ---@type boolean
|
||||
fac.induction_data_tbl[id].build = matrix[2] ---@type table
|
||||
|
||||
fac.induction_ps_tbl[id].publish("formed", matrix[1])
|
||||
|
||||
for key, val in pairs(fac.induction_data_tbl[id].build) do
|
||||
fac.induction_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
else
|
||||
if not _record_multiblock_build(id, matrix, fac.induction_data_tbl, fac.induction_ps_tbl) then
|
||||
log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id))
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- SPS
|
||||
if type(build.sps) == "table" then
|
||||
for id, sps in pairs(build.sps) do
|
||||
if not _record_multiblock_build(id, sps, fac.sps_data_tbl, fac.sps_ps_tbl) then
|
||||
log.debug(util.c("iocontrol.record_facility_builds: invalid SPS id ", id))
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- dynamic tanks
|
||||
if type(build.tanks) == "table" then
|
||||
for id, tank in pairs(build.tanks) do
|
||||
_record_multiblock_build(id, tank, fac.tank_data_tbl, fac.tank_ps_tbl, true)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug("facility builds not a table")
|
||||
valid = false
|
||||
@@ -249,16 +496,7 @@ function iocontrol.record_unit_builds(builds)
|
||||
-- boiler builds
|
||||
if type(build.boilers) == "table" then
|
||||
for b_id, boiler in pairs(build.boilers) do
|
||||
if type(unit.boiler_data_tbl[b_id]) == "table" then
|
||||
unit.boiler_data_tbl[b_id].formed = boiler[1] ---@type boolean
|
||||
unit.boiler_data_tbl[b_id].build = boiler[2] ---@type table
|
||||
|
||||
unit.boiler_ps_tbl[b_id].publish("formed", boiler[1])
|
||||
|
||||
for key, val in pairs(unit.boiler_data_tbl[b_id].build) do
|
||||
unit.boiler_ps_tbl[b_id].publish(key, val)
|
||||
end
|
||||
else
|
||||
if not _record_multiblock_build(b_id, boiler, unit.boiler_data_tbl, unit.boiler_ps_tbl) then
|
||||
log.debug(util.c(log_header, "invalid boiler id ", b_id))
|
||||
valid = false
|
||||
end
|
||||
@@ -268,27 +506,49 @@ function iocontrol.record_unit_builds(builds)
|
||||
-- turbine builds
|
||||
if type(build.turbines) == "table" then
|
||||
for t_id, turbine in pairs(build.turbines) do
|
||||
if type(unit.turbine_data_tbl[t_id]) == "table" then
|
||||
unit.turbine_data_tbl[t_id].formed = turbine[1] ---@type boolean
|
||||
unit.turbine_data_tbl[t_id].build = turbine[2] ---@type table
|
||||
|
||||
unit.turbine_ps_tbl[t_id].publish("formed", turbine[1])
|
||||
|
||||
for key, val in pairs(unit.turbine_data_tbl[t_id].build) do
|
||||
unit.turbine_ps_tbl[t_id].publish(key, val)
|
||||
end
|
||||
else
|
||||
if not _record_multiblock_build(t_id, turbine, unit.turbine_data_tbl, unit.turbine_ps_tbl) then
|
||||
log.debug(util.c(log_header, "invalid turbine id ", t_id))
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- dynamic tank builds
|
||||
if type(build.tanks) == "table" then
|
||||
for d_id, d_tank in pairs(build.tanks) do
|
||||
_record_multiblock_build(d_id, d_tank, unit.tank_data_tbl, unit.tank_ps_tbl, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Statuses
|
||||
|
||||
-- record and publish multiblock status data
|
||||
---@param entry any
|
||||
---@param data imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db
|
||||
---@param ps psil
|
||||
---@return boolean is_faulted
|
||||
local function _record_multiblock_status(entry, data, ps)
|
||||
local is_faulted = entry[1] ---@type boolean
|
||||
data.formed = entry[2] ---@type boolean
|
||||
data.state = entry[3] ---@type table
|
||||
data.tanks = entry[4] ---@type table
|
||||
|
||||
ps.publish("formed", data.formed)
|
||||
ps.publish("faulted", is_faulted)
|
||||
|
||||
for key, val in pairs(data.state) do ps.publish(key, val) end
|
||||
for key, val in pairs(data.tanks) do ps.publish(key, val) end
|
||||
|
||||
return is_faulted
|
||||
end
|
||||
|
||||
-- update facility status
|
||||
---@param status table
|
||||
---@return boolean valid
|
||||
@@ -306,7 +566,7 @@ function iocontrol.update_facility_status(status)
|
||||
|
||||
local ctl_status = status[1]
|
||||
|
||||
if type(ctl_status) == "table" and #ctl_status == 14 then
|
||||
if type(ctl_status) == "table" and #ctl_status == 16 then
|
||||
fac.all_sys_ok = ctl_status[1]
|
||||
fac.auto_ready = ctl_status[2]
|
||||
|
||||
@@ -354,6 +614,12 @@ function iocontrol.update_facility_status(status)
|
||||
io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1])
|
||||
end
|
||||
end
|
||||
|
||||
fac.auto_current_waste_product = ctl_status[15]
|
||||
fac.auto_pu_fallback_active = ctl_status[16]
|
||||
|
||||
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
||||
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
|
||||
else
|
||||
log.debug(log_header .. "control status not a table or length mismatch")
|
||||
valid = false
|
||||
@@ -390,36 +656,23 @@ function iocontrol.update_facility_status(status)
|
||||
|
||||
for id, matrix in pairs(rtu_statuses.induction) do
|
||||
if type(fac.induction_data_tbl[id]) == "table" then
|
||||
local rtu_faulted = matrix[1] ---@type boolean
|
||||
fac.induction_data_tbl[id].formed = matrix[2] ---@type boolean
|
||||
fac.induction_data_tbl[id].state = matrix[3] ---@type table
|
||||
fac.induction_data_tbl[id].tanks = matrix[4] ---@type table
|
||||
local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
|
||||
local ps = fac.induction_ps_tbl[id] ---@type psil
|
||||
|
||||
local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
|
||||
local rtu_faulted = _record_multiblock_status(matrix, data, ps)
|
||||
|
||||
fac.induction_ps_tbl[id].publish("formed", data.formed)
|
||||
fac.induction_ps_tbl[id].publish("faulted", rtu_faulted)
|
||||
|
||||
if data.formed then
|
||||
if rtu_faulted then
|
||||
fac.induction_ps_tbl[id].publish("computed_status", 3) -- faulted
|
||||
elseif data.tanks.energy_fill >= 0.99 then
|
||||
fac.induction_ps_tbl[id].publish("computed_status", 6) -- full
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
elseif data.formed then
|
||||
if data.tanks.energy_fill >= 0.99 then
|
||||
ps.publish("computed_status", 6) -- full
|
||||
elseif data.tanks.energy_fill <= 0.01 then
|
||||
fac.induction_ps_tbl[id].publish("computed_status", 5) -- empty
|
||||
ps.publish("computed_status", 5) -- empty
|
||||
else
|
||||
fac.induction_ps_tbl[id].publish("computed_status", 4) -- on-line
|
||||
ps.publish("computed_status", 4) -- on-line
|
||||
end
|
||||
else
|
||||
fac.induction_ps_tbl[id].publish("computed_status", 2) -- not formed
|
||||
end
|
||||
|
||||
for key, val in pairs(fac.induction_data_tbl[id].state) do
|
||||
fac.induction_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(fac.induction_data_tbl[id].tanks) do
|
||||
fac.induction_ps_tbl[id].publish(key, val)
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
end
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid induction matrix id ", id))
|
||||
@@ -430,6 +683,82 @@ function iocontrol.update_facility_status(status)
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- SPS statuses
|
||||
if type(rtu_statuses.sps) == "table" then
|
||||
for id = 1, #fac.sps_ps_tbl do
|
||||
if rtu_statuses.sps[id] == nil then
|
||||
-- disconnected
|
||||
fac.sps_ps_tbl[id].publish("computed_status", 1)
|
||||
end
|
||||
end
|
||||
|
||||
for id, sps in pairs(rtu_statuses.sps) do
|
||||
if type(fac.sps_data_tbl[id]) == "table" then
|
||||
local data = fac.sps_data_tbl[id] ---@type sps_session_db
|
||||
local ps = fac.sps_ps_tbl[id] ---@type psil
|
||||
|
||||
local rtu_faulted = _record_multiblock_status(sps, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
elseif data.formed then
|
||||
if data.state.process_rate > 0 then
|
||||
ps.publish("computed_status", 5) -- active
|
||||
else
|
||||
ps.publish("computed_status", 4) -- idle
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
end
|
||||
|
||||
io.facility.ps.publish("am_rate", data.state.process_rate * 1000)
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid sps id ", id))
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "sps list not a table")
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- dynamic tank statuses
|
||||
if type(rtu_statuses.tanks) == "table" then
|
||||
for id = 1, #fac.tank_ps_tbl do
|
||||
if rtu_statuses.tanks[id] == nil then
|
||||
-- disconnected
|
||||
fac.tank_ps_tbl[id].publish("computed_status", 1)
|
||||
end
|
||||
end
|
||||
|
||||
for id, tank in pairs(rtu_statuses.tanks) do
|
||||
if type(fac.tank_data_tbl[id]) == "table" then
|
||||
local data = fac.tank_data_tbl[id] ---@type dynamicv_session_db
|
||||
local ps = fac.tank_ps_tbl[id] ---@type psil
|
||||
|
||||
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
elseif data.formed then
|
||||
if data.tanks.fill >= 0.99 then
|
||||
ps.publish("computed_status", 6) -- full
|
||||
elseif data.tanks.fill < 0.20 then
|
||||
ps.publish("computed_status", 5) -- low
|
||||
else
|
||||
ps.publish("computed_status", 4) -- on-line
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
end
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "dyanmic tank list not a table")
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- environment detector status
|
||||
if type(rtu_statuses.rad_mon) == "table" then
|
||||
if #rtu_statuses.rad_mon > 0 then
|
||||
@@ -453,6 +782,16 @@ function iocontrol.update_facility_status(status)
|
||||
end
|
||||
|
||||
fac.ps.publish("rtu_count", fac.rtu_count)
|
||||
|
||||
-- alarm tone commands
|
||||
|
||||
if (type(status[3]) == "table") and (#status[3] == 8) then
|
||||
fac.alarm_tones = status[3]
|
||||
sounder.set(fac.alarm_tones)
|
||||
else
|
||||
log.debug(log_header .. "alarm tones not a table or length mismatch")
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
return valid
|
||||
@@ -472,6 +811,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
valid = false
|
||||
else
|
||||
local burn_rate_sum = 0.0
|
||||
local sna_count_sum = 0
|
||||
local pu_rate, po_rate, po_pl_rate, po_am_rate, spent_rate = 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
|
||||
-- get all unit statuses
|
||||
for i = 1, #statuses do
|
||||
@@ -480,7 +821,9 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local unit = io.units[i] ---@type ioctl_unit
|
||||
local status = statuses[i]
|
||||
|
||||
if type(status) ~= "table" or #status ~= 5 then
|
||||
local burn_rate = 0.0
|
||||
|
||||
if type(status) ~= "table" or #status ~= 6 then
|
||||
log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)")
|
||||
valid = false
|
||||
else
|
||||
@@ -515,7 +858,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
|
||||
-- if status hasn't been received, mek_status = {}
|
||||
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
|
||||
burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate
|
||||
burn_rate = unit.reactor_data.mek_status.act_burn_rate
|
||||
burn_rate_sum = burn_rate_sum + burn_rate
|
||||
end
|
||||
|
||||
if unit.reactor_data.mek_status.status then
|
||||
@@ -562,6 +906,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
if type(rtu_statuses) == "table" then
|
||||
-- boiler statuses
|
||||
if type(rtu_statuses.boilers) == "table" then
|
||||
local boil_sum = 0
|
||||
|
||||
for id = 1, #unit.boiler_ps_tbl do
|
||||
if rtu_statuses.boilers[i] == nil then
|
||||
-- disconnected
|
||||
@@ -571,40 +917,31 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
|
||||
for id, boiler in pairs(rtu_statuses.boilers) do
|
||||
if type(unit.boiler_data_tbl[id]) == "table" then
|
||||
local rtu_faulted = boiler[1] ---@type boolean
|
||||
unit.boiler_data_tbl[id].formed = boiler[2] ---@type boolean
|
||||
unit.boiler_data_tbl[id].state = boiler[3] ---@type table
|
||||
unit.boiler_data_tbl[id].tanks = boiler[4] ---@type table
|
||||
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
||||
local ps = unit.boiler_ps_tbl[id] ---@type psil
|
||||
|
||||
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
||||
|
||||
unit.boiler_ps_tbl[id].publish("formed", data.formed)
|
||||
unit.boiler_ps_tbl[id].publish("faulted", rtu_faulted)
|
||||
local rtu_faulted = _record_multiblock_status(boiler, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- faulted
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
elseif data.formed then
|
||||
boil_sum = boil_sum + data.state.boil_rate
|
||||
|
||||
if data.state.boil_rate > 0 then
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 5) -- active
|
||||
ps.publish("computed_status", 5) -- active
|
||||
else
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 4) -- idle
|
||||
ps.publish("computed_status", 4) -- idle
|
||||
end
|
||||
else
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 2) -- not formed
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.boiler_data_tbl[id].state) do
|
||||
unit.boiler_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
|
||||
unit.boiler_ps_tbl[id].publish(key, val)
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
end
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid boiler id ", id))
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("boiler_boil_sum", boil_sum)
|
||||
else
|
||||
log.debug(log_header .. "boiler list not a table")
|
||||
valid = false
|
||||
@@ -612,6 +949,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
|
||||
-- turbine statuses
|
||||
if type(rtu_statuses.turbines) == "table" then
|
||||
local flow_sum = 0
|
||||
|
||||
for id = 1, #unit.turbine_ps_tbl do
|
||||
if rtu_statuses.turbines[i] == nil then
|
||||
-- disconnected
|
||||
@@ -621,47 +960,93 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
|
||||
for id, turbine in pairs(rtu_statuses.turbines) do
|
||||
if type(unit.turbine_data_tbl[id]) == "table" then
|
||||
local rtu_faulted = turbine[1] ---@type boolean
|
||||
unit.turbine_data_tbl[id].formed = turbine[2] ---@type boolean
|
||||
unit.turbine_data_tbl[id].state = turbine[3] ---@type table
|
||||
unit.turbine_data_tbl[id].tanks = turbine[4] ---@type table
|
||||
|
||||
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
||||
local ps = unit.turbine_ps_tbl[id] ---@type psil
|
||||
|
||||
unit.turbine_ps_tbl[id].publish("formed", data.formed)
|
||||
unit.turbine_ps_tbl[id].publish("faulted", rtu_faulted)
|
||||
local rtu_faulted = _record_multiblock_status(turbine, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 3) -- faulted
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
elseif data.formed then
|
||||
flow_sum = flow_sum + data.state.flow_rate
|
||||
|
||||
if data.tanks.energy_fill >= 0.99 then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 6) -- trip
|
||||
ps.publish("computed_status", 6) -- trip
|
||||
elseif data.state.flow_rate < 100 then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- idle
|
||||
ps.publish("computed_status", 4) -- idle
|
||||
else
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 5) -- active
|
||||
ps.publish("computed_status", 5) -- active
|
||||
end
|
||||
else
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 2) -- not formed
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.turbine_data_tbl[id].state) do
|
||||
unit.turbine_ps_tbl[id].publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do
|
||||
unit.turbine_ps_tbl[id].publish(key, val)
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
end
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid turbine id ", id))
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("turbine_flow_sum", flow_sum)
|
||||
else
|
||||
log.debug(log_header .. "turbine list not a table")
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- dynamic tank statuses
|
||||
if type(rtu_statuses.tanks) == "table" then
|
||||
for id = 1, #unit.tank_ps_tbl do
|
||||
if rtu_statuses.tanks[i] == nil then
|
||||
-- disconnected
|
||||
unit.tank_ps_tbl[id].publish("computed_status", 1)
|
||||
end
|
||||
end
|
||||
|
||||
for id, tank in pairs(rtu_statuses.tanks) do
|
||||
if type(unit.tank_data_tbl[id]) == "table" then
|
||||
local data = unit.tank_data_tbl[id] ---@type dynamicv_session_db
|
||||
local ps = unit.tank_ps_tbl[id] ---@type psil
|
||||
|
||||
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
elseif data.formed then
|
||||
if data.tanks.fill >= 0.99 then
|
||||
ps.publish("computed_status", 6) -- full
|
||||
elseif data.tanks.fill < 0.20 then
|
||||
ps.publish("computed_status", 5) -- low
|
||||
else
|
||||
ps.publish("computed_status", 4) -- on-line
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
end
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||
valid = false
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "dynamic tank list not a table")
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- solar neutron activator status info
|
||||
if type(rtu_statuses.sna) == "table" then
|
||||
unit.num_snas = rtu_statuses.sna[1] ---@type integer
|
||||
unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number
|
||||
unit.sna_peak_rate = rtu_statuses.sna[3] ---@type number
|
||||
|
||||
unit.unit_ps.publish("sna_count", unit.num_snas)
|
||||
unit.unit_ps.publish("sna_prod_rate", unit.sna_prod_rate)
|
||||
unit.unit_ps.publish("sna_peak_rate", unit.sna_peak_rate)
|
||||
|
||||
sna_count_sum = sna_count_sum + unit.num_snas
|
||||
else
|
||||
log.debug(log_header .. "sna statistic list not a table")
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- environment detector status
|
||||
if type(rtu_statuses.rad_mon) == "table" then
|
||||
if #rtu_statuses.rad_mon > 0 then
|
||||
@@ -739,12 +1124,17 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local unit_state = status[5]
|
||||
|
||||
if type(unit_state) == "table" then
|
||||
if #unit_state == 5 then
|
||||
if #unit_state == 6 then
|
||||
unit.waste_mode = unit_state[5]
|
||||
unit.waste_product = unit_state[6]
|
||||
|
||||
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
|
||||
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
|
||||
unit.unit_ps.publish("U_WasteMode", unit_state[3])
|
||||
unit.unit_ps.publish("U_AutoReady", unit_state[4])
|
||||
unit.unit_ps.publish("U_AutoDegraded", unit_state[5])
|
||||
unit.unit_ps.publish("U_AutoReady", unit_state[3])
|
||||
unit.unit_ps.publish("U_AutoDegraded", unit_state[4])
|
||||
unit.unit_ps.publish("U_AutoWaste", unit.waste_mode == types.WASTE_MODE.AUTO)
|
||||
unit.unit_ps.publish("U_WasteMode", unit.waste_mode)
|
||||
unit.unit_ps.publish("U_WasteProduct", unit.waste_product)
|
||||
else
|
||||
log.debug(log_header .. "unit state length mismatch")
|
||||
valid = false
|
||||
@@ -753,18 +1143,81 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
log.debug(log_header .. "unit state not a table")
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- valve states
|
||||
local valve_states = status[6]
|
||||
|
||||
if type(valve_states) == "table" then
|
||||
if #valve_states == 5 then
|
||||
unit.unit_ps.publish("V_pu_conn", valve_states[1] > 0)
|
||||
unit.unit_ps.publish("V_pu_state", valve_states[1] == 2)
|
||||
unit.unit_ps.publish("V_po_conn", valve_states[2] > 0)
|
||||
unit.unit_ps.publish("V_po_state", valve_states[2] == 2)
|
||||
unit.unit_ps.publish("V_pl_conn", valve_states[3] > 0)
|
||||
unit.unit_ps.publish("V_pl_state", valve_states[3] == 2)
|
||||
unit.unit_ps.publish("V_am_conn", valve_states[4] > 0)
|
||||
unit.unit_ps.publish("V_am_state", valve_states[4] == 2)
|
||||
unit.unit_ps.publish("V_emc_conn", valve_states[5] > 0)
|
||||
unit.unit_ps.publish("V_emc_state", valve_states[5] == 2)
|
||||
else
|
||||
log.debug(log_header .. "valve states length mismatch")
|
||||
valid = false
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "valve states not a table")
|
||||
valid = false
|
||||
end
|
||||
|
||||
-- determine waste production for this unit, add to statistics
|
||||
|
||||
local is_pu = unit.waste_product == types.WASTE_PRODUCT.PLUTONIUM
|
||||
local waste_rate = burn_rate / 10.0
|
||||
|
||||
local u_spent_rate = waste_rate
|
||||
local u_pu_rate = util.trinary(is_pu, waste_rate, 0.0)
|
||||
local u_po_rate = util.trinary(not is_pu, math.min(waste_rate, unit.sna_prod_rate), 0.0)
|
||||
|
||||
unit.unit_ps.publish("pu_rate", u_pu_rate)
|
||||
unit.unit_ps.publish("po_rate", u_po_rate)
|
||||
|
||||
unit.unit_ps.publish("sna_in", util.trinary(is_pu, 0, burn_rate))
|
||||
|
||||
if unit.waste_product == types.WASTE_PRODUCT.POLONIUM then
|
||||
unit.unit_ps.publish("po_pl_rate", u_po_rate)
|
||||
unit.unit_ps.publish("po_am_rate", 0)
|
||||
po_pl_rate = po_pl_rate + u_po_rate
|
||||
elseif unit.waste_product == types.WASTE_PRODUCT.ANTI_MATTER then
|
||||
unit.unit_ps.publish("po_pl_rate", 0)
|
||||
unit.unit_ps.publish("po_am_rate", u_po_rate)
|
||||
po_am_rate = po_am_rate + u_po_rate
|
||||
u_spent_rate = 0
|
||||
else
|
||||
unit.unit_ps.publish("po_pl_rate", 0)
|
||||
unit.unit_ps.publish("po_am_rate", 0)
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("ws_rate", u_spent_rate)
|
||||
|
||||
pu_rate = pu_rate + u_pu_rate
|
||||
po_rate = po_rate + u_po_rate
|
||||
spent_rate = spent_rate + u_spent_rate
|
||||
end
|
||||
end
|
||||
|
||||
io.facility.ps.publish("burn_sum", burn_rate_sum)
|
||||
|
||||
-- update alarm sounder
|
||||
sounder.eval(io.units)
|
||||
io.facility.ps.publish("sna_count", sna_count_sum)
|
||||
io.facility.ps.publish("pu_rate", pu_rate)
|
||||
io.facility.ps.publish("po_rate", po_rate)
|
||||
io.facility.ps.publish("po_pl_rate", po_pl_rate)
|
||||
io.facility.ps.publish("po_am_rate", po_am_rate)
|
||||
io.facility.ps.publish("spent_waste_rate", spent_rate)
|
||||
end
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
-- get the IO controller database
|
||||
function iocontrol.get_db() return io end
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ local FAC_COMMAND = comms.FAC_COMMAND
|
||||
local UNIT_COMMAND = comms.UNIT_COMMAND
|
||||
|
||||
local PROCESS = types.PROCESS
|
||||
local PRODUCT = types.WASTE_PRODUCT
|
||||
|
||||
---@class process_controller
|
||||
local process = {}
|
||||
@@ -24,7 +25,9 @@ local self = {
|
||||
burn_target = 0.0,
|
||||
charge_target = 0.0,
|
||||
gen_target = 0.0,
|
||||
limits = {}
|
||||
limits = {},
|
||||
waste_product = PRODUCT.PLUTONIUM,
|
||||
pu_fallback = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,19 +51,23 @@ function process.init(iocontrol, coord_comms)
|
||||
log.error("process.init(): failed to load coordinator settings file")
|
||||
end
|
||||
|
||||
-- facility auto control configuration
|
||||
local config = settings.get("PROCESS") ---@type coord_auto_config|nil
|
||||
|
||||
if type(config) == "table" then
|
||||
self.config.mode = config.mode
|
||||
self.config.burn_target = config.burn_target
|
||||
self.config.charge_target = config.charge_target
|
||||
self.config.gen_target = config.gen_target
|
||||
self.config.limits = config.limits
|
||||
self.config.waste_product = config.waste_product
|
||||
self.config.pu_fallback = config.pu_fallback
|
||||
|
||||
self.io.facility.ps.publish("process_mode", self.config.mode)
|
||||
self.io.facility.ps.publish("process_burn_target", self.config.burn_target)
|
||||
self.io.facility.ps.publish("process_charge_target", self.config.charge_target)
|
||||
self.io.facility.ps.publish("process_gen_target", self.config.gen_target)
|
||||
self.io.facility.ps.publish("process_waste_product", self.config.waste_product)
|
||||
self.io.facility.ps.publish("process_pu_fallback", self.config.pu_fallback)
|
||||
|
||||
for id = 1, math.min(#self.config.limits, self.io.facility.num_units) do
|
||||
local unit = self.io.units[id] ---@type ioctl_unit
|
||||
@@ -68,20 +75,24 @@ function process.init(iocontrol, coord_comms)
|
||||
end
|
||||
|
||||
log.info("PROCESS: loaded auto control settings from coord.settings")
|
||||
|
||||
-- notify supervisor of auto waste config
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, self.config.waste_product)
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, self.config.pu_fallback)
|
||||
end
|
||||
|
||||
local waste_mode = settings.get("WASTE_MODES") ---@type table|nil
|
||||
|
||||
if type(waste_mode) == "table" then
|
||||
for id, mode in pairs(waste_mode) do
|
||||
-- unit waste states
|
||||
local waste_modes = settings.get("WASTE_MODES") ---@type table|nil
|
||||
if type(waste_modes) == "table" then
|
||||
for id, mode in pairs(waste_modes) do
|
||||
self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode)
|
||||
end
|
||||
|
||||
log.info("PROCESS: loaded waste mode settings from coord.settings")
|
||||
log.info("PROCESS: loaded unit waste mode settings from coord.settings")
|
||||
end
|
||||
|
||||
-- unit priority groups
|
||||
local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil
|
||||
|
||||
if type(prio_groups) == "table" then
|
||||
for id, group in pairs(prio_groups) do
|
||||
self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group)
|
||||
@@ -137,7 +148,7 @@ end
|
||||
-- set waste mode
|
||||
---@param id integer unit ID
|
||||
---@param mode integer waste mode
|
||||
function process.set_waste(id, mode)
|
||||
function process.set_unit_waste(id, mode)
|
||||
-- publish so that if it fails then it gets reset
|
||||
self.io.units[id].unit_ps.publish("U_WasteMode", mode)
|
||||
|
||||
@@ -153,7 +164,7 @@ function process.set_waste(id, mode)
|
||||
settings.set("WASTE_MODES", waste_mode)
|
||||
|
||||
if not settings.save("/coord.settings") then
|
||||
log.error("process.set_waste(): failed to save coordinator settings file")
|
||||
log.error("process.set_unit_waste(): failed to save coordinator settings file")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -204,6 +215,24 @@ end
|
||||
-- AUTO PROCESS CONTROL --
|
||||
--------------------------
|
||||
|
||||
-- write auto process control to config file
|
||||
local function _write_auto_config()
|
||||
-- attempt to load settings
|
||||
if not settings.load("/coord.settings") then
|
||||
log.warning("process._write_auto_config(): failed to load coordinator settings file")
|
||||
end
|
||||
|
||||
-- save config
|
||||
settings.set("PROCESS", self.config)
|
||||
local saved = settings.save("/coord.settings")
|
||||
|
||||
if not saved then
|
||||
log.warning("process._write_auto_config(): failed to save coordinator settings file")
|
||||
end
|
||||
|
||||
return not not saved
|
||||
end
|
||||
|
||||
-- stop automatic process control
|
||||
function process.stop_auto()
|
||||
self.comms.send_fac_command(FAC_COMMAND.STOP)
|
||||
@@ -216,6 +245,30 @@ function process.start_auto()
|
||||
log.debug("PROCESS: START AUTO CTL")
|
||||
end
|
||||
|
||||
-- set automatic process control waste mode
|
||||
---@param product WASTE_PRODUCT waste product for auto control
|
||||
function process.set_process_waste(product)
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, product)
|
||||
|
||||
log.debug(util.c("PROCESS: SET WASTE ", product))
|
||||
|
||||
-- update config table and save
|
||||
self.config.waste_product = product
|
||||
_write_auto_config()
|
||||
end
|
||||
|
||||
-- set automatic process control plutonium fallback
|
||||
---@param enabled boolean whether to enable plutonium fallback
|
||||
function process.set_pu_fallback(enabled)
|
||||
self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, enabled)
|
||||
|
||||
log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled))
|
||||
|
||||
-- update config table and save
|
||||
self.config.pu_fallback = enabled
|
||||
_write_auto_config()
|
||||
end
|
||||
|
||||
-- save process control settings
|
||||
---@param mode PROCESS control mode
|
||||
---@param burn_target number burn rate target
|
||||
@@ -223,29 +276,17 @@ end
|
||||
---@param gen_target number generation rate target
|
||||
---@param limits table unit burn rate limits
|
||||
function process.save(mode, burn_target, charge_target, gen_target, limits)
|
||||
-- attempt to load settings
|
||||
if not settings.load("/coord.settings") then
|
||||
log.warning("process.save(): failed to load coordinator settings file")
|
||||
end
|
||||
log.debug("PROCESS: SAVE")
|
||||
|
||||
-- config table
|
||||
self.config = {
|
||||
mode = mode,
|
||||
burn_target = burn_target,
|
||||
charge_target = charge_target,
|
||||
gen_target = gen_target,
|
||||
limits = limits
|
||||
}
|
||||
-- update config table
|
||||
self.config.mode = mode
|
||||
self.config.burn_target = burn_target
|
||||
self.config.charge_target = charge_target
|
||||
self.config.gen_target = gen_target
|
||||
self.config.limits = limits
|
||||
|
||||
-- save config
|
||||
settings.set("PROCESS", self.config)
|
||||
local saved = settings.save("/coord.settings")
|
||||
|
||||
if not saved then
|
||||
log.warning("process.save(): failed to save coordinator settings file")
|
||||
end
|
||||
|
||||
self.io.facility.save_cfg_ack(saved)
|
||||
self.io.facility.save_cfg_ack(_write_auto_config())
|
||||
end
|
||||
|
||||
-- handle a start command acknowledgement
|
||||
@@ -258,16 +299,33 @@ function process.start_ack_handle(response)
|
||||
self.config.charge_target = response[4]
|
||||
self.config.gen_target = response[5]
|
||||
|
||||
for i = 1, #response[6] do
|
||||
for i = 1, math.min(#response[6], self.io.facility.num_units) do
|
||||
self.config.limits[i] = response[6][i]
|
||||
|
||||
local unit = self.io.units[i] ---@type ioctl_unit
|
||||
unit.unit_ps.publish("burn_limit", self.config.limits[i])
|
||||
end
|
||||
|
||||
self.io.facility.ps.publish("auto_mode", self.config.mode)
|
||||
self.io.facility.ps.publish("burn_target", self.config.burn_target)
|
||||
self.io.facility.ps.publish("charge_target", self.config.charge_target)
|
||||
self.io.facility.ps.publish("gen_target", self.config.gen_target)
|
||||
self.io.facility.ps.publish("process_mode", self.config.mode)
|
||||
self.io.facility.ps.publish("process_burn_target", self.config.burn_target)
|
||||
self.io.facility.ps.publish("process_charge_target", self.config.charge_target)
|
||||
self.io.facility.ps.publish("process_gen_target", self.config.gen_target)
|
||||
|
||||
self.io.facility.start_ack(ack)
|
||||
end
|
||||
|
||||
-- record waste product state after attempting to change it
|
||||
---@param response WASTE_PRODUCT supervisor waste product state
|
||||
function process.waste_ack_handle(response)
|
||||
self.config.waste_product = response
|
||||
self.io.facility.ps.publish("process_waste_product", response)
|
||||
end
|
||||
|
||||
-- record plutonium fallback state after attempting to change it
|
||||
---@param response boolean supervisor plutonium fallback state
|
||||
function process.pu_fb_ack_handle(response)
|
||||
self.config.pu_fallback = response
|
||||
self.io.facility.ps.publish("process_pu_fallback", response)
|
||||
end
|
||||
|
||||
return process
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
local pgi = require("coordinator.ui.pgi")
|
||||
|
||||
local flow_view = require("coordinator.ui.layout.flow_view")
|
||||
local panel_view = require("coordinator.ui.layout.front_panel")
|
||||
local main_view = require("coordinator.ui.layout.main_view")
|
||||
local unit_view = require("coordinator.ui.layout.unit_view")
|
||||
|
||||
@@ -21,10 +26,14 @@ local engine = {
|
||||
monitors = nil, ---@type monitors_struct|nil
|
||||
dmesg_window = nil, ---@type table|nil
|
||||
ui_ready = false,
|
||||
fp_ready = false,
|
||||
ui = {
|
||||
front_panel = nil, ---@type graphics_element|nil
|
||||
main_display = nil, ---@type graphics_element|nil
|
||||
flow_display = nil, ---@type graphics_element|nil
|
||||
unit_displays = {}
|
||||
}
|
||||
},
|
||||
disable_flow_view = false
|
||||
}
|
||||
|
||||
-- init a display to the "default", but set text scale to 0.5
|
||||
@@ -42,39 +51,44 @@ local function _init_display(monitor)
|
||||
end
|
||||
end
|
||||
|
||||
-- disable the flow view
|
||||
---@param disable boolean
|
||||
function renderer.legacy_disable_flow_view(disable)
|
||||
engine.disable_flow_view = disable
|
||||
end
|
||||
|
||||
-- link to the monitor peripherals
|
||||
---@param monitors monitors_struct
|
||||
function renderer.set_displays(monitors)
|
||||
engine.monitors = monitors
|
||||
end
|
||||
|
||||
-- check if the renderer is configured to use a given monitor peripheral
|
||||
---@nodiscard
|
||||
---@param periph table peripheral
|
||||
---@return boolean is_used
|
||||
function renderer.is_monitor_used(periph)
|
||||
if engine.monitors ~= nil then
|
||||
if engine.monitors.primary == periph then
|
||||
return true
|
||||
else
|
||||
for _, monitor in ipairs(engine.monitors.unit_displays) do
|
||||
if monitor == periph then return true end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
-- report to front panel as connected
|
||||
iocontrol.fp_monitor_state("main", engine.monitors.primary ~= nil)
|
||||
iocontrol.fp_monitor_state("flow", engine.monitors.flow ~= nil)
|
||||
for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end
|
||||
end
|
||||
|
||||
-- init all displays in use by the renderer
|
||||
function renderer.init_displays()
|
||||
-- init primary monitor
|
||||
-- init primary and flow monitors
|
||||
_init_display(engine.monitors.primary)
|
||||
if not engine.disable_flow_view then _init_display(engine.monitors.flow) end
|
||||
|
||||
-- init unit displays
|
||||
for _, monitor in ipairs(engine.monitors.unit_displays) do
|
||||
_init_display(monitor)
|
||||
end
|
||||
|
||||
-- init terminal
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
-- set overridden colors
|
||||
for i = 1, #style.fp.colors do
|
||||
term.setPaletteColor(style.fp.colors[i].c, style.fp.colors[i].hex)
|
||||
end
|
||||
end
|
||||
|
||||
-- check main display width
|
||||
@@ -85,6 +99,14 @@ function renderer.validate_main_display_width()
|
||||
return w == 164
|
||||
end
|
||||
|
||||
-- check flow display width
|
||||
---@nodiscard
|
||||
---@return boolean width_okay
|
||||
function renderer.validate_flow_display_width()
|
||||
local w, _ = engine.monitors.flow.getSize()
|
||||
return w == 164
|
||||
end
|
||||
|
||||
-- check display sizes
|
||||
---@nodiscard
|
||||
---@return boolean valid all unit display dimensions OK
|
||||
@@ -109,6 +131,51 @@ function renderer.init_dmesg()
|
||||
log.direct_dmesg(engine.dmesg_window)
|
||||
end
|
||||
|
||||
-- start the coordinator front panel
|
||||
function renderer.start_fp()
|
||||
if not engine.fp_ready then
|
||||
-- show front panel view on terminal
|
||||
engine.ui.front_panel = DisplayBox{window=term.native(),fg_bg=style.fp.root}
|
||||
panel_view(engine.ui.front_panel, #engine.monitors.unit_displays)
|
||||
|
||||
-- start flasher callback task
|
||||
flasher.run()
|
||||
|
||||
-- report front panel as ready
|
||||
engine.fp_ready = true
|
||||
end
|
||||
end
|
||||
|
||||
-- close out the front panel
|
||||
function renderer.close_fp()
|
||||
if engine.fp_ready then
|
||||
if not engine.ui_ready then
|
||||
-- stop blinking indicators
|
||||
flasher.clear()
|
||||
end
|
||||
|
||||
-- disable PGI
|
||||
pgi.unlink()
|
||||
|
||||
-- hide to stop animation callbacks and clear root UI elements
|
||||
engine.ui.front_panel.hide()
|
||||
engine.ui.front_panel = nil
|
||||
engine.fp_ready = false
|
||||
|
||||
-- restore colors
|
||||
for i = 1, #style.colors do
|
||||
local r, g, b = term.nativePaletteColor(style.colors[i].c)
|
||||
term.setPaletteColor(style.colors[i].c, r, g, b)
|
||||
end
|
||||
|
||||
-- reset terminal
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- start the coordinator GUI
|
||||
function renderer.start_ui()
|
||||
if not engine.ui_ready then
|
||||
@@ -116,13 +183,21 @@ function renderer.start_ui()
|
||||
engine.dmesg_window.setVisible(false)
|
||||
|
||||
-- show main view on main monitor
|
||||
engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root}
|
||||
main_view(engine.ui.main_display)
|
||||
if engine.monitors.primary ~= nil then
|
||||
engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root}
|
||||
main_view(engine.ui.main_display)
|
||||
end
|
||||
|
||||
-- show flow view on flow monitor
|
||||
if engine.monitors.flow ~= nil then
|
||||
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
|
||||
flow_view(engine.ui.flow_display)
|
||||
end
|
||||
|
||||
-- show unit views on unit displays
|
||||
for i = 1, #engine.monitors.unit_displays do
|
||||
engine.ui.unit_displays[i] = DisplayBox{window=engine.monitors.unit_displays[i],fg_bg=style.root}
|
||||
unit_view(engine.ui.unit_displays[i], i)
|
||||
for idx, display in pairs(engine.monitors.unit_displays) do
|
||||
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
|
||||
unit_view(engine.ui.unit_displays[idx], idx)
|
||||
end
|
||||
|
||||
-- start flasher callback task
|
||||
@@ -135,18 +210,22 @@ end
|
||||
|
||||
-- close out the UI
|
||||
function renderer.close_ui()
|
||||
-- stop blinking indicators
|
||||
flasher.clear()
|
||||
if not engine.fp_ready then
|
||||
-- stop blinking indicators
|
||||
flasher.clear()
|
||||
end
|
||||
|
||||
-- delete element trees
|
||||
if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end
|
||||
for _, display in ipairs(engine.ui.unit_displays) do display.delete() end
|
||||
if engine.ui.flow_display ~= nil then engine.ui.flow_display.delete() end
|
||||
for _, display in pairs(engine.ui.unit_displays) do display.delete() end
|
||||
|
||||
-- report ui as not ready
|
||||
engine.ui_ready = false
|
||||
|
||||
-- clear root UI elements
|
||||
engine.ui.main_display = nil
|
||||
engine.ui.flow_display = nil
|
||||
engine.ui.unit_displays = {}
|
||||
|
||||
-- clear unit monitors
|
||||
@@ -157,22 +236,145 @@ function renderer.close_ui()
|
||||
engine.dmesg_window.redraw()
|
||||
end
|
||||
|
||||
-- is the front panel ready?
|
||||
---@nodiscard
|
||||
---@return boolean ready
|
||||
function renderer.fp_ready() return engine.fp_ready end
|
||||
|
||||
-- is the UI ready?
|
||||
---@nodiscard
|
||||
---@return boolean ready
|
||||
function renderer.ui_ready() return engine.ui_ready end
|
||||
|
||||
-- handle a monitor peripheral being disconnected
|
||||
---@param device table monitor
|
||||
---@return boolean is_used if the monitor is one of the configured monitors
|
||||
function renderer.handle_disconnect(device)
|
||||
local is_used = false
|
||||
|
||||
if engine.monitors ~= nil then
|
||||
if engine.monitors.primary == device then
|
||||
if engine.ui.main_display ~= nil then
|
||||
-- delete element tree and clear root UI elements
|
||||
engine.ui.main_display.delete()
|
||||
end
|
||||
|
||||
is_used = true
|
||||
engine.monitors.primary = nil
|
||||
engine.ui.main_display = nil
|
||||
|
||||
iocontrol.fp_monitor_state("main", false)
|
||||
elseif engine.monitors.flow == device then
|
||||
if engine.ui.flow_display ~= nil then
|
||||
-- delete element tree and clear root UI elements
|
||||
engine.ui.flow_display.delete()
|
||||
end
|
||||
|
||||
is_used = true
|
||||
engine.monitors.flow = nil
|
||||
engine.ui.flow_display = nil
|
||||
|
||||
iocontrol.fp_monitor_state("flow", false)
|
||||
else
|
||||
for idx, monitor in pairs(engine.monitors.unit_displays) do
|
||||
if monitor == device then
|
||||
if engine.ui.unit_displays[idx] ~= nil then
|
||||
engine.ui.unit_displays[idx].delete()
|
||||
end
|
||||
|
||||
is_used = true
|
||||
engine.monitors.unit_displays[idx] = nil
|
||||
engine.ui.unit_displays[idx] = nil
|
||||
|
||||
iocontrol.fp_monitor_state(idx, false)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return is_used
|
||||
end
|
||||
|
||||
-- handle a monitor peripheral being reconnected
|
||||
---@param name string monitor name
|
||||
---@param device table monitor
|
||||
---@return boolean is_used if the monitor is one of the configured monitors
|
||||
function renderer.handle_reconnect(name, device)
|
||||
local is_used = false
|
||||
|
||||
if engine.monitors ~= nil then
|
||||
if engine.monitors.primary_name == name then
|
||||
is_used = true
|
||||
_init_display(device)
|
||||
engine.monitors.primary = device
|
||||
|
||||
local disp_x, disp_y = engine.monitors.primary.getSize()
|
||||
engine.dmesg_window.reposition(1, 1, disp_x, disp_y, engine.monitors.primary)
|
||||
|
||||
if engine.ui_ready and (engine.ui.main_display == nil) then
|
||||
engine.dmesg_window.setVisible(false)
|
||||
|
||||
engine.ui.main_display = DisplayBox{window=device,fg_bg=style.root}
|
||||
main_view(engine.ui.main_display)
|
||||
else
|
||||
engine.dmesg_window.setVisible(true)
|
||||
engine.dmesg_window.redraw()
|
||||
end
|
||||
|
||||
iocontrol.fp_monitor_state("main", true)
|
||||
elseif engine.monitors.flow_name == name then
|
||||
is_used = true
|
||||
_init_display(device)
|
||||
engine.monitors.flow = device
|
||||
|
||||
if engine.ui_ready and (engine.ui.flow_display == nil) then
|
||||
engine.ui.flow_display = DisplayBox{window=device,fg_bg=style.root}
|
||||
flow_view(engine.ui.flow_display)
|
||||
end
|
||||
|
||||
iocontrol.fp_monitor_state("flow", true)
|
||||
else
|
||||
for idx, monitor in ipairs(engine.monitors.unit_name_map) do
|
||||
if monitor == name then
|
||||
is_used = true
|
||||
_init_display(device)
|
||||
engine.monitors.unit_displays[idx] = device
|
||||
|
||||
if engine.ui_ready and (engine.ui.unit_displays[idx] == nil) then
|
||||
engine.ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
|
||||
unit_view(engine.ui.unit_displays[idx], idx)
|
||||
end
|
||||
|
||||
iocontrol.fp_monitor_state(idx, true)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return is_used
|
||||
end
|
||||
|
||||
|
||||
-- handle a touch event
|
||||
---@param event mouse_interaction|nil
|
||||
function renderer.handle_mouse(event)
|
||||
if engine.ui_ready and event ~= nil then
|
||||
if event.monitor == engine.monitors.primary_name then
|
||||
engine.ui.main_display.handle_mouse(event)
|
||||
else
|
||||
for id, monitor in ipairs(engine.monitors.unit_name_map) do
|
||||
if event.monitor == monitor then
|
||||
local layout = engine.ui.unit_displays[id] ---@type graphics_element
|
||||
layout.handle_mouse(event)
|
||||
if event ~= nil then
|
||||
if engine.fp_ready and event.monitor == "terminal" then
|
||||
engine.ui.front_panel.handle_mouse(event)
|
||||
elseif engine.ui_ready then
|
||||
if event.monitor == engine.monitors.primary_name then
|
||||
engine.ui.main_display.handle_mouse(event)
|
||||
elseif event.monitor == engine.monitors.flow_name then
|
||||
engine.ui.flow_display.handle_mouse(event)
|
||||
else
|
||||
for id, monitor in ipairs(engine.monitors.unit_name_map) do
|
||||
if event.monitor == monitor then
|
||||
local layout = engine.ui.unit_displays[id] ---@type graphics_element
|
||||
layout.handle_mouse(event)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local util = require("scada-common.util")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("coordinator.config")
|
||||
local config = require("coordinator.config")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local pocket = require("coordinator.session.pocket")
|
||||
local pocket = require("coordinator.session.pocket")
|
||||
|
||||
local apisessions = {}
|
||||
|
||||
local self = {
|
||||
modem = nil,
|
||||
nic = nil,
|
||||
next_id = 0,
|
||||
sessions = {}
|
||||
}
|
||||
@@ -31,7 +32,7 @@ local function _api_handle_outq(session)
|
||||
if msg ~= nil then
|
||||
if msg.qtype == mqueue.TYPE.PACKET then
|
||||
-- handle a packet to be sent
|
||||
self.modem.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message.raw_sendable())
|
||||
self.nic.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message)
|
||||
elseif msg.qtype == mqueue.TYPE.COMMAND then
|
||||
-- handle instruction/notification
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
@@ -58,7 +59,7 @@ local function _shutdown(session)
|
||||
while session.out_queue.ready() do
|
||||
local msg = session.out_queue.pop()
|
||||
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then
|
||||
self.modem.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message.raw_sendable())
|
||||
self.nic.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,15 +69,9 @@ end
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
-- initialize apisessions
|
||||
---@param modem table
|
||||
function apisessions.init(modem)
|
||||
self.modem = modem
|
||||
end
|
||||
|
||||
-- re-link the modem
|
||||
---@param modem table
|
||||
function apisessions.relink_modem(modem)
|
||||
self.modem = modem
|
||||
---@param nic nic
|
||||
function apisessions.init(nic)
|
||||
self.nic = nic
|
||||
end
|
||||
|
||||
-- find a session by remote port
|
||||
@@ -118,6 +113,7 @@ function apisessions.establish_session(source_addr, version)
|
||||
|
||||
setmetatable(pkt_s, mt)
|
||||
|
||||
iocontrol.fp_pkt_connected(id, version, source_addr)
|
||||
log.debug(util.c("[API] established new session: ", pkt_s))
|
||||
|
||||
self.next_id = id + 1
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local util = require("scada-common.util")
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local pocket = {}
|
||||
|
||||
@@ -9,8 +11,6 @@ local PROTOCOL = comms.PROTOCOL
|
||||
-- local CAPI_TYPE = comms.CAPI_TYPE
|
||||
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
||||
|
||||
local println = util.println
|
||||
|
||||
-- retry time constants in ms
|
||||
-- local INITIAL_WAIT = 1500
|
||||
-- local RETRY_PERIOD = 1000
|
||||
@@ -69,6 +69,7 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
local function _close()
|
||||
self.conn_watchdog.cancel()
|
||||
self.connected = false
|
||||
iocontrol.fp_pkt_disconnected(id)
|
||||
end
|
||||
|
||||
-- send a CAPI packet
|
||||
@@ -140,6 +141,8 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
|
||||
-- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "PKT TT = " .. (srv_now - api_send) .. "ms")
|
||||
|
||||
iocontrol.fp_pkt_rtt(id, self.last_rtt)
|
||||
else
|
||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
||||
end
|
||||
@@ -172,7 +175,6 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
function public.close()
|
||||
_close()
|
||||
_send_mgmt(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
println("connection to pocket session " .. id .. " closed by server")
|
||||
log.info(log_header .. "session closed by server")
|
||||
end
|
||||
|
||||
@@ -211,7 +213,6 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
|
||||
-- exit if connection was closed
|
||||
if not self.connected then
|
||||
println("connection to pocket session " .. id .. " closed by remote host")
|
||||
log.info(log_header .. "session closed by remote host")
|
||||
return self.connected
|
||||
end
|
||||
|
||||
@@ -2,269 +2,25 @@
|
||||
-- Alarm Sounder
|
||||
--
|
||||
|
||||
local audio = require("scada-common.audio")
|
||||
local log = require("scada-common.log")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
---@class sounder
|
||||
local sounder = {}
|
||||
|
||||
-- note: max samples = 0x20000 (128 * 1024 samples)
|
||||
|
||||
local _2_PI = 2 * math.pi -- 2 whole pies, hope you're hungry
|
||||
local _DRATE = 48000 -- 48kHz audio
|
||||
local _MAX_VAL = 127 / 2 -- max signed integer in this 8-bit audio
|
||||
local _05s_SAMPLES = 24000 -- half a second worth of samples
|
||||
|
||||
local test_alarms = { false, false, false, false, false, false, false, false, false, false, false, false }
|
||||
|
||||
local alarm_ctl = {
|
||||
speaker = nil,
|
||||
volume = 0.5,
|
||||
playing = false,
|
||||
num_active = 0,
|
||||
next_block = 1,
|
||||
-- split audio up into 0.5s samples so specific components can be ended quicker
|
||||
quad_buffer = { {}, {}, {}, {} }
|
||||
stream = audio.new_stream()
|
||||
}
|
||||
|
||||
-- sounds modeled after https://www.e2s.com/references-and-guidelines/listen-and-download-alarm-tones
|
||||
|
||||
local T_340Hz_Int_2Hz = 1
|
||||
local T_544Hz_440Hz_Alt = 2
|
||||
local T_660Hz_Int_125ms = 3
|
||||
local T_745Hz_Int_1Hz = 4
|
||||
local T_800Hz_Int = 5
|
||||
local T_800Hz_1000Hz_Alt = 6
|
||||
local T_1000Hz_Int = 7
|
||||
local T_1800Hz_Int_4Hz = 8
|
||||
|
||||
local TONES = {
|
||||
{ active = false, component = { {}, {}, {}, {} } }, -- 340Hz @ 2Hz Intermittent
|
||||
{ active = false, component = { {}, {}, {}, {} } }, -- 544Hz 100mS / 440Hz 400mS Alternating
|
||||
{ active = false, component = { {}, {}, {}, {} } }, -- 660Hz @ 125ms On 125ms Off
|
||||
{ active = false, component = { {}, {}, {}, {} } }, -- 745Hz @ 1Hz Intermittent
|
||||
{ active = false, component = { {}, {}, {}, {} } }, -- 800Hz @ 0.25s On 1.75s Off
|
||||
{ active = false, component = { {}, {}, {}, {} } }, -- 800/1000Hz @ 0.25s Alternating
|
||||
{ active = false, component = { {}, {}, {}, {} } }, -- 1KHz 1s on, 1s off Intermittent
|
||||
{ active = false, component = { {}, {}, {}, {} } } -- 1.8KHz @ 4Hz Intermittent
|
||||
}
|
||||
|
||||
-- calculate how many samples are in the given number of milliseconds
|
||||
---@nodiscard
|
||||
---@param ms integer milliseconds
|
||||
---@return integer samples
|
||||
local function ms_to_samples(ms) return math.floor(ms * 48) end
|
||||
|
||||
--#region Tone Generation (the Maths)
|
||||
|
||||
-- 340Hz @ 2Hz Intermittent
|
||||
local function gen_tone_1()
|
||||
local t, dt = 0, _2_PI * 340 / _DRATE
|
||||
|
||||
for i = 1, _05s_SAMPLES do
|
||||
local val = math.floor(math.sin(t) * _MAX_VAL)
|
||||
TONES[1].component[1][i] = val
|
||||
TONES[1].component[3][i] = val
|
||||
TONES[1].component[2][i] = 0
|
||||
TONES[1].component[4][i] = 0
|
||||
t = (t + dt) % _2_PI
|
||||
end
|
||||
end
|
||||
|
||||
-- 544Hz 100mS / 440Hz 400mS Alternating
|
||||
local function gen_tone_2()
|
||||
local t1, dt1 = 0, _2_PI * 544 / _DRATE
|
||||
local t2, dt2 = 0, _2_PI * 440 / _DRATE
|
||||
local alternate_at = ms_to_samples(100)
|
||||
|
||||
for i = 1, _05s_SAMPLES do
|
||||
local value
|
||||
|
||||
if i <= alternate_at then
|
||||
value = math.floor(math.sin(t1) * _MAX_VAL)
|
||||
t1 = (t1 + dt1) % _2_PI
|
||||
else
|
||||
value = math.floor(math.sin(t2) * _MAX_VAL)
|
||||
t2 = (t2 + dt2) % _2_PI
|
||||
end
|
||||
|
||||
TONES[2].component[1][i] = value
|
||||
TONES[2].component[2][i] = value
|
||||
TONES[2].component[3][i] = value
|
||||
TONES[2].component[4][i] = value
|
||||
end
|
||||
end
|
||||
|
||||
-- 660Hz @ 125ms On 125ms Off
|
||||
local function gen_tone_3()
|
||||
local elapsed_samples = 0
|
||||
local alternate_after = ms_to_samples(125)
|
||||
local alternate_at = alternate_after
|
||||
local mode = true
|
||||
|
||||
local t, dt = 0, _2_PI * 660 / _DRATE
|
||||
|
||||
for set = 1, 4 do
|
||||
for i = 1, _05s_SAMPLES do
|
||||
if mode then
|
||||
local val = math.floor(math.sin(t) * _MAX_VAL)
|
||||
TONES[3].component[set][i] = val
|
||||
t = (t + dt) % _2_PI
|
||||
else
|
||||
t = 0
|
||||
TONES[3].component[set][i] = 0
|
||||
end
|
||||
|
||||
if elapsed_samples == alternate_at then
|
||||
mode = not mode
|
||||
alternate_at = elapsed_samples + alternate_after
|
||||
end
|
||||
|
||||
elapsed_samples = elapsed_samples + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 745Hz @ 1Hz Intermittent
|
||||
local function gen_tone_4()
|
||||
local t, dt = 0, _2_PI * 745 / _DRATE
|
||||
|
||||
for i = 1, _05s_SAMPLES do
|
||||
local val = math.floor(math.sin(t) * _MAX_VAL)
|
||||
TONES[4].component[1][i] = val
|
||||
TONES[4].component[3][i] = val
|
||||
TONES[4].component[2][i] = 0
|
||||
TONES[4].component[4][i] = 0
|
||||
t = (t + dt) % _2_PI
|
||||
end
|
||||
end
|
||||
|
||||
-- 800Hz @ 0.25s On 1.75s Off
|
||||
local function gen_tone_5()
|
||||
local t, dt = 0, _2_PI * 800 / _DRATE
|
||||
local stop_at = ms_to_samples(250)
|
||||
|
||||
for i = 1, _05s_SAMPLES do
|
||||
local val = math.floor(math.sin(t) * _MAX_VAL)
|
||||
|
||||
if i > stop_at then
|
||||
TONES[5].component[1][i] = val
|
||||
else
|
||||
TONES[5].component[1][i] = 0
|
||||
end
|
||||
|
||||
TONES[5].component[2][i] = 0
|
||||
TONES[5].component[3][i] = 0
|
||||
TONES[5].component[4][i] = 0
|
||||
|
||||
t = (t + dt) % _2_PI
|
||||
end
|
||||
end
|
||||
|
||||
-- 1000/800Hz @ 0.25s Alternating
|
||||
local function gen_tone_6()
|
||||
local t1, dt1 = 0, _2_PI * 1000 / _DRATE
|
||||
local t2, dt2 = 0, _2_PI * 800 / _DRATE
|
||||
|
||||
local alternate_at = ms_to_samples(250)
|
||||
|
||||
for i = 1, _05s_SAMPLES do
|
||||
local val
|
||||
if i <= alternate_at then
|
||||
val = math.floor(math.sin(t1) * _MAX_VAL)
|
||||
t1 = (t1 + dt1) % _2_PI
|
||||
else
|
||||
val = math.floor(math.sin(t2) * _MAX_VAL)
|
||||
t2 = (t2 + dt2) % _2_PI
|
||||
end
|
||||
|
||||
TONES[6].component[1][i] = val
|
||||
TONES[6].component[2][i] = val
|
||||
TONES[6].component[3][i] = val
|
||||
TONES[6].component[4][i] = val
|
||||
end
|
||||
end
|
||||
|
||||
-- 1KHz 1s on, 1s off Intermittent
|
||||
local function gen_tone_7()
|
||||
local t, dt = 0, _2_PI * 1000 / _DRATE
|
||||
|
||||
for i = 1, _05s_SAMPLES do
|
||||
local val = math.floor(math.sin(t) * _MAX_VAL)
|
||||
TONES[7].component[1][i] = val
|
||||
TONES[7].component[2][i] = val
|
||||
TONES[7].component[3][i] = 0
|
||||
TONES[7].component[4][i] = 0
|
||||
t = (t + dt) % _2_PI
|
||||
end
|
||||
end
|
||||
|
||||
-- 1800Hz @ 4Hz Intermittent
|
||||
local function gen_tone_8()
|
||||
local t, dt = 0, _2_PI * 1800 / _DRATE
|
||||
|
||||
local off_at = ms_to_samples(250)
|
||||
|
||||
for i = 1, _05s_SAMPLES do
|
||||
local val = 0
|
||||
|
||||
if i <= off_at then
|
||||
val = math.floor(math.sin(t) * _MAX_VAL)
|
||||
t = (t + dt) % _2_PI
|
||||
end
|
||||
|
||||
TONES[8].component[1][i] = val
|
||||
TONES[8].component[2][i] = val
|
||||
TONES[8].component[3][i] = val
|
||||
TONES[8].component[4][i] = val
|
||||
end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
-- hard audio limiter
|
||||
---@nodiscard
|
||||
---@param output number output level
|
||||
---@return number limited -128.0 to 127.0
|
||||
local function limit(output)
|
||||
return math.max(-128, math.min(127, output))
|
||||
end
|
||||
|
||||
-- zero the alarm audio buffer
|
||||
local function zero()
|
||||
for i = 1, 4 do
|
||||
for s = 1, _05s_SAMPLES do alarm_ctl.quad_buffer[i][s] = 0 end
|
||||
end
|
||||
end
|
||||
|
||||
-- add an alarm to the output buffer
|
||||
---@param alarm_idx integer tone ID
|
||||
local function add(alarm_idx)
|
||||
alarm_ctl.num_active = alarm_ctl.num_active + 1
|
||||
TONES[alarm_idx].active = true
|
||||
|
||||
for i = 1, 4 do
|
||||
for s = 1, _05s_SAMPLES do
|
||||
alarm_ctl.quad_buffer[i][s] = limit(alarm_ctl.quad_buffer[i][s] + TONES[alarm_idx].component[i][s])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- start audio or continue audio on buffer empty
|
||||
---@return boolean success successfully added buffer to audio output
|
||||
local function play()
|
||||
if not alarm_ctl.playing then
|
||||
alarm_ctl.playing = true
|
||||
alarm_ctl.next_block = 1
|
||||
|
||||
return sounder.continue()
|
||||
else
|
||||
return true
|
||||
end
|
||||
else return true end
|
||||
end
|
||||
|
||||
-- initialize the annunciator alarm system
|
||||
@@ -273,23 +29,10 @@ end
|
||||
function sounder.init(speaker, volume)
|
||||
alarm_ctl.speaker = speaker
|
||||
alarm_ctl.speaker.stop()
|
||||
|
||||
alarm_ctl.volume = volume
|
||||
alarm_ctl.playing = false
|
||||
alarm_ctl.num_active = 0
|
||||
alarm_ctl.next_block = 1
|
||||
alarm_ctl.stream.stop()
|
||||
|
||||
zero()
|
||||
|
||||
-- generate tones
|
||||
gen_tone_1()
|
||||
gen_tone_2()
|
||||
gen_tone_3()
|
||||
gen_tone_4()
|
||||
gen_tone_5()
|
||||
gen_tone_6()
|
||||
gen_tone_7()
|
||||
gen_tone_8()
|
||||
audio.generate_tones()
|
||||
end
|
||||
|
||||
-- reconnect the speaker peripheral
|
||||
@@ -297,173 +40,40 @@ end
|
||||
function sounder.reconnect(speaker)
|
||||
alarm_ctl.speaker = speaker
|
||||
alarm_ctl.playing = false
|
||||
alarm_ctl.next_block = 1
|
||||
alarm_ctl.num_active = 0
|
||||
for id = 1, #TONES do TONES[id].active = false end
|
||||
alarm_ctl.stream.stop()
|
||||
end
|
||||
|
||||
-- check alarm state to enable/disable alarms
|
||||
---@param units table|nil unit list or nil to use test mode
|
||||
function sounder.eval(units)
|
||||
local changed = false
|
||||
local any_active = false
|
||||
local new_states = { false, false, false, false, false, false, false, false }
|
||||
local alarms = { false, false, false, false, false, false, false, false, false, false, false, false }
|
||||
-- set alarm tones
|
||||
---@param states table alarm tone commands from supervisor
|
||||
function sounder.set(states)
|
||||
-- set tone states
|
||||
for id = 1, #states do alarm_ctl.stream.set_active(id, states[id]) end
|
||||
|
||||
if units ~= nil then
|
||||
-- check all alarms for all units
|
||||
for i = 1, #units do
|
||||
local unit = units[i] ---@type ioctl_unit
|
||||
for id = 1, #unit.alarms do
|
||||
alarms[id] = alarms[id] or (unit.alarms[id] == ALARM_STATE.TRIPPED)
|
||||
end
|
||||
end
|
||||
else
|
||||
alarms = test_alarms
|
||||
end
|
||||
|
||||
-- containment breach is worst case CRITICAL alarm, this takes priority
|
||||
if alarms[ALARM.ContainmentBreach] then
|
||||
new_states[T_1800Hz_Int_4Hz] = true
|
||||
else
|
||||
-- critical damage is highest priority CRITICAL level alarm
|
||||
if alarms[ALARM.CriticalDamage] then
|
||||
new_states[T_660Hz_Int_125ms] = true
|
||||
else
|
||||
-- EMERGENCY level alarms + URGENT over temp
|
||||
if alarms[ALARM.ReactorDamage] or alarms[ALARM.ReactorOverTemp] or alarms[ALARM.ReactorWasteLeak] then
|
||||
new_states[T_544Hz_440Hz_Alt] = true
|
||||
-- URGENT level turbine trip
|
||||
elseif alarms[ALARM.TurbineTrip] then
|
||||
new_states[T_745Hz_Int_1Hz] = true
|
||||
-- URGENT level reactor lost
|
||||
elseif alarms[ALARM.ReactorLost] then
|
||||
new_states[T_340Hz_Int_2Hz] = true
|
||||
-- TIMELY level alarms
|
||||
elseif alarms[ALARM.ReactorHighTemp] or alarms[ALARM.ReactorHighWaste] or alarms[ALARM.RCSTransient] then
|
||||
new_states[T_800Hz_Int] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- check RPS transient URGENT level alarm
|
||||
if alarms[ALARM.RPSTransient] then
|
||||
new_states[T_1000Hz_Int] = true
|
||||
-- disable really painful audio combination
|
||||
new_states[T_340Hz_Int_2Hz] = false
|
||||
end
|
||||
end
|
||||
|
||||
-- radiation is a big concern, always play this CRITICAL level alarm if active
|
||||
if alarms[ALARM.ContainmentRadiation] then
|
||||
new_states[T_800Hz_1000Hz_Alt] = true
|
||||
-- we are going to disable the RPS trip alarm audio due to conflict, and if it was enabled
|
||||
-- then we can re-enable the reactor lost alarm audio since it doesn't painfully combine with this one
|
||||
if new_states[T_1000Hz_Int] and alarms[ALARM.ReactorLost] then new_states[T_340Hz_Int_2Hz] = true end
|
||||
-- it sounds *really* bad if this is in conjunction with these other tones, so disable them
|
||||
new_states[T_745Hz_Int_1Hz] = false
|
||||
new_states[T_800Hz_Int] = false
|
||||
new_states[T_1000Hz_Int] = false
|
||||
end
|
||||
|
||||
-- check if any changed, check if any active, update active flags
|
||||
for id = 1, #TONES do
|
||||
if new_states[id] ~= TONES[id].active then
|
||||
TONES[id].active = new_states[id]
|
||||
changed = true
|
||||
end
|
||||
|
||||
if TONES[id].active then any_active = true end
|
||||
end
|
||||
|
||||
-- zero and re-add tones if changed
|
||||
if changed then
|
||||
zero()
|
||||
|
||||
for id = 1, #TONES do
|
||||
if TONES[id].active then add(id) end
|
||||
end
|
||||
end
|
||||
|
||||
if any_active then play() else sounder.stop() end
|
||||
-- re-compute output if needed, then play audio if available
|
||||
if alarm_ctl.stream.is_recompute_needed() then alarm_ctl.stream.compute_buffer() end
|
||||
if alarm_ctl.stream.has_next_block() then play() else sounder.stop() end
|
||||
end
|
||||
|
||||
-- stop all audio and clear output buffer
|
||||
function sounder.stop()
|
||||
alarm_ctl.playing = false
|
||||
alarm_ctl.speaker.stop()
|
||||
alarm_ctl.next_block = 1
|
||||
alarm_ctl.num_active = 0
|
||||
for id = 1, #TONES do TONES[id].active = false end
|
||||
zero()
|
||||
alarm_ctl.stream.stop()
|
||||
end
|
||||
|
||||
-- continue audio on buffer empty
|
||||
---@return boolean success successfully added buffer to audio output
|
||||
function sounder.continue()
|
||||
local success = false
|
||||
|
||||
if alarm_ctl.playing then
|
||||
if alarm_ctl.speaker ~= nil and #alarm_ctl.quad_buffer[alarm_ctl.next_block] > 0 then
|
||||
local success = alarm_ctl.speaker.playAudio(alarm_ctl.quad_buffer[alarm_ctl.next_block], alarm_ctl.volume)
|
||||
|
||||
alarm_ctl.next_block = alarm_ctl.next_block + 1
|
||||
if alarm_ctl.next_block > 4 then alarm_ctl.next_block = 1 end
|
||||
|
||||
if not success then
|
||||
log.debug("SOUNDER: error playing audio")
|
||||
end
|
||||
|
||||
return success
|
||||
else
|
||||
return false
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--#region Test Functions
|
||||
|
||||
function sounder.test_1() add(1) play() end -- play tone T_340Hz_Int_2Hz
|
||||
function sounder.test_2() add(2) play() end -- play tone T_544Hz_440Hz_Alt
|
||||
function sounder.test_3() add(3) play() end -- play tone T_660Hz_Int_125ms
|
||||
function sounder.test_4() add(4) play() end -- play tone T_745Hz_Int_1Hz
|
||||
function sounder.test_5() add(5) play() end -- play tone T_800Hz_Int
|
||||
function sounder.test_6() add(6) play() end -- play tone T_800Hz_1000Hz_Alt
|
||||
function sounder.test_7() add(7) play() end -- play tone T_1000Hz_Int
|
||||
function sounder.test_8() add(8) play() end -- play tone T_1800Hz_Int_4Hz
|
||||
|
||||
function sounder.test_breach(active) test_alarms[ALARM.ContainmentBreach] = active end ---@param active boolean
|
||||
function sounder.test_rad(active) test_alarms[ALARM.ContainmentRadiation] = active end ---@param active boolean
|
||||
function sounder.test_lost(active) test_alarms[ALARM.ReactorLost] = active end ---@param active boolean
|
||||
function sounder.test_crit(active) test_alarms[ALARM.CriticalDamage] = active end ---@param active boolean
|
||||
function sounder.test_dmg(active) test_alarms[ALARM.ReactorDamage] = active end ---@param active boolean
|
||||
function sounder.test_overtemp(active) test_alarms[ALARM.ReactorOverTemp] = active end ---@param active boolean
|
||||
function sounder.test_hightemp(active) test_alarms[ALARM.ReactorHighTemp] = active end ---@param active boolean
|
||||
function sounder.test_wasteleak(active) test_alarms[ALARM.ReactorWasteLeak] = active end ---@param active boolean
|
||||
function sounder.test_highwaste(active) test_alarms[ALARM.ReactorHighWaste] = active end ---@param active boolean
|
||||
function sounder.test_rps(active) test_alarms[ALARM.RPSTransient] = active end ---@param active boolean
|
||||
function sounder.test_rcs(active) test_alarms[ALARM.RCSTransient] = active end ---@param active boolean
|
||||
function sounder.test_turbinet(active) test_alarms[ALARM.TurbineTrip] = active end ---@param active boolean
|
||||
|
||||
-- power rescaling limiter test
|
||||
function sounder.test_power_scale()
|
||||
local start = util.time_ms()
|
||||
|
||||
zero()
|
||||
|
||||
for id = 1, #TONES do
|
||||
if TONES[id].active then
|
||||
for i = 1, 4 do
|
||||
for s = 1, _05s_SAMPLES do
|
||||
alarm_ctl.quad_buffer[i][s] = limit(alarm_ctl.quad_buffer[i][s] +
|
||||
(TONES[id].component[i][s] / math.sqrt(alarm_ctl.num_active)))
|
||||
end
|
||||
end
|
||||
if alarm_ctl.speaker ~= nil and alarm_ctl.stream.has_next_block() then
|
||||
success = alarm_ctl.speaker.playAudio(alarm_ctl.stream.get_next_block(), alarm_ctl.volume)
|
||||
if not success then log.error("SOUNDER: error playing audio") end
|
||||
end
|
||||
end
|
||||
|
||||
log.debug("SOUNDER: power rescale test took " .. (util.time_ms() - start) .. "ms")
|
||||
return success
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
return sounder
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
require("/initenv").init_env()
|
||||
|
||||
local comms = require("scada-common.comms")
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local network = require("scada-common.network")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
@@ -20,7 +22,7 @@ local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v0.16.1"
|
||||
local COORDINATOR_VERSION = "v1.0.2"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -29,7 +31,7 @@ local log_graphics = coordinator.log_graphics
|
||||
local log_sys = coordinator.log_sys
|
||||
local log_boot = coordinator.log_boot
|
||||
local log_comms = coordinator.log_comms
|
||||
local log_comms_connecting = coordinator.log_comms_connecting
|
||||
local log_crypto = coordinator.log_crypto
|
||||
|
||||
----------------------------------------
|
||||
-- config validation
|
||||
@@ -78,8 +80,11 @@ local function main()
|
||||
-- mount connected devices
|
||||
ppm.mount_all()
|
||||
|
||||
-- report versions/init fp PSIL
|
||||
iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
|
||||
|
||||
-- setup monitors
|
||||
local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS)
|
||||
local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS, config.DISABLE_FLOW_VIEW == true)
|
||||
if not configured or monitors == nil then
|
||||
println("startup> monitor setup failed")
|
||||
log.fatal("monitor configuration failed")
|
||||
@@ -87,6 +92,7 @@ local function main()
|
||||
end
|
||||
|
||||
-- init renderer
|
||||
renderer.legacy_disable_flow_view(config.DISABLE_FLOW_VIEW == true)
|
||||
renderer.set_displays(monitors)
|
||||
renderer.init_displays()
|
||||
|
||||
@@ -94,6 +100,10 @@ local function main()
|
||||
println("startup> main display must be 8 blocks wide")
|
||||
log.fatal("main display not wide enough")
|
||||
return
|
||||
elseif (config.DISABLE_FLOW_VIEW ~= true) and not renderer.validate_flow_display_width() then
|
||||
println("startup> flow display must be 8 blocks wide")
|
||||
log.fatal("flow display not wide enough")
|
||||
return
|
||||
elseif not renderer.validate_unit_display_sizes() then
|
||||
println("startup> one or more unit display dimensions incorrect; they must be 4x4 blocks")
|
||||
log.fatal("unit display dimensions incorrect")
|
||||
@@ -125,12 +135,19 @@ local function main()
|
||||
sounder.init(speaker, config.SOUNDER_VOLUME)
|
||||
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
|
||||
log_sys("annunciator alarm configured")
|
||||
iocontrol.fp_has_speaker(true)
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- setup communications
|
||||
----------------------------------------
|
||||
|
||||
-- message authentication init
|
||||
if type(config.AUTH_KEY) == "string" then
|
||||
local init_time = network.init_mac(config.AUTH_KEY)
|
||||
log_crypto("HMAC init took " .. init_time .. "ms")
|
||||
end
|
||||
|
||||
-- get the communications modem
|
||||
local modem = ppm.get_wireless_modem()
|
||||
if modem == nil then
|
||||
@@ -140,6 +157,7 @@ local function main()
|
||||
return
|
||||
else
|
||||
log_comms("wireless modem connected")
|
||||
iocontrol.fp_has_modem(true)
|
||||
end
|
||||
|
||||
-- create connection watchdog
|
||||
@@ -147,9 +165,10 @@ local function main()
|
||||
conn_watchdog.cancel()
|
||||
log.debug("startup> conn watchdog created")
|
||||
|
||||
-- start comms, open all channels
|
||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.CRD_CHANNEL, config.SVR_CHANNEL,
|
||||
config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog)
|
||||
-- create network interface then setup comms
|
||||
local nic = network.nic(modem)
|
||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, config.NUM_UNITS, config.CRD_CHANNEL,
|
||||
config.SVR_CHANNEL, config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog)
|
||||
log.debug("startup> comms init")
|
||||
log_comms("comms initialized")
|
||||
|
||||
@@ -158,78 +177,54 @@ local function main()
|
||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||
|
||||
----------------------------------------
|
||||
-- connect to the supervisor
|
||||
-- start front panel & UI start function
|
||||
----------------------------------------
|
||||
|
||||
-- attempt to connect to the supervisor or exit
|
||||
local function init_connect_sv()
|
||||
local tick_waiting, task_done = log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SVR_CHANNEL)
|
||||
log_graphics("starting front panel UI...")
|
||||
|
||||
-- attempt to establish a connection with the supervisory computer
|
||||
if not coord_comms.sv_connect(60, tick_waiting, task_done) then
|
||||
log_sys("supervisor connection failed, shutting down...")
|
||||
log.fatal("failed to connect to supervisor")
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if not init_connect_sv() then
|
||||
println("startup> failed to connect to supervisor")
|
||||
log_sys("system shutdown")
|
||||
local fp_ok, fp_message = pcall(renderer.start_fp)
|
||||
if not fp_ok then
|
||||
renderer.close_fp()
|
||||
log_graphics(util.c("front panel UI error: ", fp_message))
|
||||
println_ts("front panel UI creation failed")
|
||||
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
|
||||
return
|
||||
else
|
||||
log_sys("supervisor connected, proceeding to UI start")
|
||||
end
|
||||
else log_graphics("front panel ready") end
|
||||
|
||||
----------------------------------------
|
||||
-- start the UI
|
||||
----------------------------------------
|
||||
|
||||
-- start up the UI
|
||||
-- start up the main UI
|
||||
---@return boolean ui_ok started ok
|
||||
local function init_start_ui()
|
||||
log_graphics("starting UI...")
|
||||
local function start_main_ui()
|
||||
log_graphics("starting main UI...")
|
||||
|
||||
local draw_start = util.time_ms()
|
||||
|
||||
local ui_ok, message = pcall(renderer.start_ui)
|
||||
local ui_ok, ui_message = pcall(renderer.start_ui)
|
||||
if not ui_ok then
|
||||
renderer.close_ui()
|
||||
log_graphics(util.c("UI crashed: ", message))
|
||||
println_ts("UI crashed")
|
||||
log.fatal(util.c("GUI crashed with error ", message))
|
||||
log_graphics(util.c("main UI error: ", ui_message))
|
||||
log.fatal(util.c("main GUI render failed with error ", ui_message))
|
||||
else
|
||||
log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||
|
||||
-- start clock
|
||||
loop_clock.start()
|
||||
log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||
end
|
||||
|
||||
return ui_ok
|
||||
end
|
||||
|
||||
local ui_ok = init_start_ui()
|
||||
|
||||
----------------------------------------
|
||||
-- main event loop
|
||||
----------------------------------------
|
||||
|
||||
local link_failed = false
|
||||
local ui_ok = true
|
||||
local date_format = util.trinary(config.TIME_24_HOUR, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y")
|
||||
|
||||
local no_modem = false
|
||||
-- start clock
|
||||
loop_clock.start()
|
||||
|
||||
if ui_ok then
|
||||
-- start connection watchdog
|
||||
conn_watchdog.feed()
|
||||
log.debug("startup> conn watchdog started")
|
||||
|
||||
log_sys("system started successfully")
|
||||
end
|
||||
log_sys("system started successfully")
|
||||
|
||||
-- main event loop
|
||||
while ui_ok do
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
|
||||
-- handle event
|
||||
@@ -239,33 +234,36 @@ local function main()
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
-- we only really care if this is our wireless modem
|
||||
if device == modem then
|
||||
no_modem = true
|
||||
-- if it is another modem, handle other peripheral losses separately
|
||||
if nic.is_modem(device) then
|
||||
nic.disconnect()
|
||||
log_sys("comms modem disconnected")
|
||||
println_ts("wireless modem disconnected!")
|
||||
|
||||
-- close out UI
|
||||
renderer.close_ui()
|
||||
local other_modem = ppm.get_wireless_modem()
|
||||
if other_modem then
|
||||
log_sys("found another wireless modem, using it for comms")
|
||||
nic.connect(other_modem)
|
||||
else
|
||||
-- close out main UI
|
||||
renderer.close_ui()
|
||||
|
||||
-- alert user to status
|
||||
log_sys("awaiting comms modem reconnect...")
|
||||
-- alert user to status
|
||||
log_sys("awaiting comms modem reconnect...")
|
||||
|
||||
iocontrol.fp_has_modem(false)
|
||||
end
|
||||
else
|
||||
log_sys("non-comms modem disconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
if renderer.is_monitor_used(device) then
|
||||
-- "halt and catch fire" style handling
|
||||
local msg = "lost a configured monitor, system will now exit"
|
||||
println_ts(msg)
|
||||
log_sys(msg)
|
||||
break
|
||||
if renderer.handle_disconnect(device) then
|
||||
log_sys("lost a configured monitor")
|
||||
else
|
||||
log_sys("lost unused monitor, ignoring")
|
||||
log_sys("lost an unused monitor")
|
||||
end
|
||||
elseif type == "speaker" then
|
||||
local msg = "lost alarm sounder speaker"
|
||||
println_ts(msg)
|
||||
log_sys(msg)
|
||||
log_sys("lost alarm sounder speaker")
|
||||
iocontrol.fp_has_speaker(false)
|
||||
end
|
||||
end
|
||||
elseif event == "peripheral" then
|
||||
@@ -273,34 +271,50 @@ local function main()
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
if device.isWireless() then
|
||||
if device.isWireless() and not nic.is_connected() then
|
||||
-- reconnected modem
|
||||
no_modem = false
|
||||
modem = device
|
||||
coord_comms.reconnect_modem(modem)
|
||||
|
||||
log_sys("comms modem reconnected")
|
||||
println_ts("wireless modem reconnected.")
|
||||
|
||||
-- re-init system
|
||||
if not init_connect_sv() then break end
|
||||
ui_ok = init_start_ui()
|
||||
nic.connect(device)
|
||||
iocontrol.fp_has_modem(true)
|
||||
elseif device.isWireless() then
|
||||
log.info("unused wireless modem reconnected")
|
||||
else
|
||||
log_sys("wired modem reconnected")
|
||||
end
|
||||
-- elseif type == "monitor" then
|
||||
-- not supported, system will exit on loss of in-use monitors
|
||||
elseif type == "monitor" then
|
||||
if renderer.handle_reconnect(param1, device) then
|
||||
log_sys(util.c("configured monitor ", param1, " reconnected"))
|
||||
else
|
||||
log_sys(util.c("unused monitor ", param1, " connected"))
|
||||
end
|
||||
elseif type == "speaker" then
|
||||
local msg = "alarm sounder speaker reconnected"
|
||||
println_ts(msg)
|
||||
log_sys(msg)
|
||||
log_sys("alarm sounder speaker reconnected")
|
||||
sounder.reconnect(device)
|
||||
iocontrol.fp_has_speaker(true)
|
||||
end
|
||||
end
|
||||
elseif event == "timer" then
|
||||
if loop_clock.is_clock(param1) then
|
||||
-- main loop tick
|
||||
|
||||
-- toggle heartbeat
|
||||
iocontrol.heartbeat()
|
||||
|
||||
-- maintain connection
|
||||
if nic.is_connected() then
|
||||
local ok, start_ui = coord_comms.try_connect()
|
||||
if not ok then
|
||||
link_failed = true
|
||||
log_sys("supervisor connection failed, shutting down...")
|
||||
log.fatal("failed to connect to supervisor")
|
||||
break
|
||||
elseif start_ui then
|
||||
log_sys("supervisor connected, proceeding to main UI start")
|
||||
ui_ok = start_main_ui()
|
||||
if not ui_ok then break end
|
||||
end
|
||||
end
|
||||
|
||||
-- iterate sessions
|
||||
apisessions.iterate_all()
|
||||
|
||||
@@ -308,25 +322,19 @@ local function main()
|
||||
apisessions.free_all_closed()
|
||||
|
||||
-- update date and time string for main display
|
||||
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
|
||||
if coord_comms.is_linked() then
|
||||
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
|
||||
end
|
||||
|
||||
loop_clock.start()
|
||||
elseif conn_watchdog.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
local msg = "supervisor server timeout"
|
||||
log_comms(msg)
|
||||
println_ts(msg)
|
||||
log_comms("supervisor server timeout")
|
||||
|
||||
-- close connection, UI, and stop sounder
|
||||
-- close connection, main UI, and stop sounder
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
|
||||
if not no_modem then
|
||||
-- try to re-connect to the supervisor
|
||||
if not init_connect_sv() then break end
|
||||
ui_ok = init_start_ui()
|
||||
end
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
|
||||
@@ -339,25 +347,19 @@ local function main()
|
||||
elseif event == "modem_message" then
|
||||
-- got a packet
|
||||
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
coord_comms.handle_packet(packet)
|
||||
|
||||
-- check if it was a disconnect
|
||||
if not coord_comms.is_linked() then
|
||||
-- handle then check if it was a disconnect
|
||||
if coord_comms.handle_packet(packet) then
|
||||
log_comms("supervisor closed connection")
|
||||
|
||||
-- close connection, UI, and stop sounder
|
||||
-- close connection, main UI, and stop sounder
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
|
||||
if not no_modem then
|
||||
-- try to re-connect to the supervisor
|
||||
if not init_connect_sv() then break end
|
||||
ui_ok = init_start_ui()
|
||||
end
|
||||
end
|
||||
elseif event == "monitor_touch" then
|
||||
-- handle a monitor touch event
|
||||
elseif event == "monitor_touch" or 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 == "speaker_audio_empty" then
|
||||
-- handle speaker buffer emptied
|
||||
@@ -366,10 +368,17 @@ local function main()
|
||||
|
||||
-- check for termination request
|
||||
if event == "terminate" or ppm.should_terminate() then
|
||||
println_ts("terminate requested, closing connections...")
|
||||
log_comms("terminate requested, closing supervisor connection...")
|
||||
-- handle supervisor connection
|
||||
coord_comms.try_connect(true)
|
||||
|
||||
if coord_comms.is_linked() then
|
||||
log_comms("terminate requested, closing supervisor connection...")
|
||||
else link_failed = true end
|
||||
|
||||
coord_comms.close()
|
||||
log_comms("supervisor connection closed")
|
||||
|
||||
-- handle API sessions
|
||||
log_comms("closing api sessions...")
|
||||
apisessions.close_all()
|
||||
log_comms("api sessions closed")
|
||||
@@ -378,15 +387,20 @@ local function main()
|
||||
end
|
||||
|
||||
renderer.close_ui()
|
||||
renderer.close_fp()
|
||||
sounder.stop()
|
||||
log_sys("system shutdown")
|
||||
|
||||
if link_failed then println_ts("failed to connect to supervisor") end
|
||||
if not ui_ok then println_ts("main UI creation failed") end
|
||||
|
||||
println_ts("exited")
|
||||
log.info("exited")
|
||||
end
|
||||
|
||||
if not xpcall(main, crash.handler) then
|
||||
pcall(renderer.close_ui)
|
||||
pcall(renderer.close_fp)
|
||||
pcall(sounder.stop)
|
||||
crash.exit()
|
||||
else
|
||||
|
||||
@@ -83,9 +83,7 @@ local function new_view(root, x, y, data, ps, id)
|
||||
local function calc_saturation(val)
|
||||
if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then
|
||||
return val / data.build.transfer_cap
|
||||
else
|
||||
return 0
|
||||
end
|
||||
else return 0 end
|
||||
end
|
||||
|
||||
charge.register(ps, "energy_fill", charge.update)
|
||||
|
||||
48
coordinator/ui/components/pkt_entry.lua
Normal file
48
coordinator/ui/components/pkt_entry.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
--
|
||||
-- Pocket Connection Entry
|
||||
--
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- create a pocket list entry
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer PKT session ID
|
||||
local function init(parent, id)
|
||||
local ps = iocontrol.get_db().fp.ps
|
||||
|
||||
-- root div
|
||||
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
|
||||
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=cpair(colors.black,colors.white)}
|
||||
|
||||
local ps_prefix = "pkt_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
|
||||
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
|
||||
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
|
||||
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
return init
|
||||
@@ -15,8 +15,10 @@ local TextBox = require("graphics.elements.textbox")
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||
local RadIndicator = require("graphics.elements.indicators.rad")
|
||||
local StateIndicator = require("graphics.elements.indicators.state")
|
||||
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
|
||||
|
||||
local Checkbox = require("graphics.elements.controls.checkbox")
|
||||
local HazardButton = require("graphics.elements.controls.hazard_button")
|
||||
local RadioButton = require("graphics.elements.controls.radio_button")
|
||||
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric")
|
||||
@@ -26,6 +28,16 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local lu_cpair = style.lu_colors
|
||||
local hzd_fg_bg = style.hzd_fg_bg
|
||||
local dis_colors = style.dis_colors
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_yel = style.ind_yel
|
||||
local ind_red = style.ind_red
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
local period = core.flasher.PERIOD
|
||||
|
||||
-- new process control view
|
||||
@@ -38,12 +50,7 @@ local function new_view(root, x, y)
|
||||
local facility = iocontrol.get_db().facility
|
||||
local units = iocontrol.get_db().units
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local hzd_fg_bg = cpair(colors.white, colors.gray)
|
||||
local lu_cpair = cpair(colors.gray, colors.gray)
|
||||
local dis_colors = cpair(colors.white, colors.lightGray)
|
||||
|
||||
local main = Div{parent=root,width=104,height=24,x=x,y=y}
|
||||
local main = Div{parent=root,width=128,height=24,x=x,y=y}
|
||||
|
||||
local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg}
|
||||
local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg}
|
||||
@@ -51,20 +58,22 @@ local function new_view(root, x, y)
|
||||
facility.scram_ack = scram.on_response
|
||||
facility.ack_alarms_ack = ack_a.on_response
|
||||
|
||||
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)}
|
||||
local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)}
|
||||
local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn}
|
||||
local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green}
|
||||
local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=ind_grn}
|
||||
local sps = IndicatorLight{parent=main,label="SPS Connected",colors=ind_grn}
|
||||
|
||||
all_ok.register(facility.ps, "all_sys_ok", all_ok.update)
|
||||
ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end)
|
||||
rad_mon.register(facility.ps, "rad_computed_status", rad_mon.update)
|
||||
ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end)
|
||||
sps.register(facility.sps_ps_tbl[1], "computed_status", function (status) sps.update(status > 1) end)
|
||||
|
||||
main.line_break()
|
||||
|
||||
local auto_ready = IndicatorLight{parent=main,label="Configured Units Ready",colors=cpair(colors.green,colors.red)}
|
||||
local auto_act = IndicatorLight{parent=main,label="Process Active",colors=cpair(colors.green,colors.gray)}
|
||||
local auto_ramp = IndicatorLight{parent=main,label="Process Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local auto_sat = IndicatorLight{parent=main,label="Min/Max Burn Rate",colors=cpair(colors.yellow,colors.gray)}
|
||||
local auto_ready = IndicatorLight{parent=main,label="Configured Units Ready",colors=ind_grn}
|
||||
local auto_act = IndicatorLight{parent=main,label="Process Active",colors=ind_grn}
|
||||
local auto_ramp = IndicatorLight{parent=main,label="Process Ramping",colors=ind_wht,flash=true,period=period.BLINK_250_MS}
|
||||
local auto_sat = IndicatorLight{parent=main,label="Min/Max Burn Rate",colors=ind_yel}
|
||||
|
||||
auto_ready.register(facility.ps, "auto_ready", auto_ready.update)
|
||||
auto_act.register(facility.ps, "auto_active", auto_act.update)
|
||||
@@ -73,12 +82,12 @@ local function new_view(root, x, y)
|
||||
|
||||
main.line_break()
|
||||
|
||||
local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local matrix_dc = IndicatorLight{parent=main,label="Matrix Disconnected",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local matrix_fill = IndicatorLight{parent=main,label="Matrix Charge High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local unit_crit = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local fac_rad_h = IndicatorLight{parent=main,label="Facility Radiation High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local gen_fault = IndicatorLight{parent=main,label="Gen. Control Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
local matrix_dc = IndicatorLight{parent=main,label="Matrix Disconnected",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||
local matrix_fill = IndicatorLight{parent=main,label="Matrix Charge High",colors=ind_red,flash=true,period=period.BLINK_500_MS}
|
||||
local unit_crit = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
local fac_rad_h = IndicatorLight{parent=main,label="Facility Radiation High",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
local gen_fault = IndicatorLight{parent=main,label="Gen. Control Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||
|
||||
auto_scram.register(facility.ps, "auto_scram", auto_scram.update)
|
||||
matrix_dc.register(facility.ps, "as_matrix_dc", matrix_dc.update)
|
||||
@@ -99,7 +108,7 @@ local function new_view(root, x, y)
|
||||
-- process control --
|
||||
---------------------
|
||||
|
||||
local proc = Div{parent=main,width=78,height=24,x=27,y=1}
|
||||
local proc = Div{parent=main,width=103,height=24,x=27,y=1}
|
||||
|
||||
-----------------------------
|
||||
-- process control targets --
|
||||
@@ -148,46 +157,77 @@ local function new_view(root, x, y)
|
||||
|
||||
local rate_limits = {}
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
local unit = units[i] ---@type ioctl_unit
|
||||
for i = 1, 4 do
|
||||
local unit
|
||||
local tag_fg_bg = cpair(colors.gray,colors.white)
|
||||
local lim_fg_bg = cpair(colors.lightGray,colors.white)
|
||||
local ctl_fg = colors.lightGray
|
||||
local cur_fg_bg = cpair(colors.lightGray,colors.white)
|
||||
local cur_lu = colors.lightGray
|
||||
|
||||
if i <= facility.num_units then
|
||||
unit = units[i] ---@type ioctl_unit
|
||||
tag_fg_bg = cpair(colors.black,colors.lightBlue)
|
||||
lim_fg_bg = bw_fg_bg
|
||||
ctl_fg = colors.gray
|
||||
cur_fg_bg = cpair(colors.black,colors.brown)
|
||||
cur_lu = colors.black
|
||||
end
|
||||
|
||||
local _y = ((i - 1) * 5) + 1
|
||||
|
||||
local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||
local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg}
|
||||
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2}
|
||||
|
||||
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg}
|
||||
local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(ctl_fg,colors.white)}
|
||||
local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=lim_fg_bg}
|
||||
TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1}
|
||||
|
||||
rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max)
|
||||
rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value)
|
||||
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg}
|
||||
|
||||
local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(colors.black,colors.black),width=14,fg_bg=cpair(colors.black,colors.brown)}
|
||||
if i <= facility.num_units then
|
||||
rate_limits[i] = lim
|
||||
rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max)
|
||||
rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value)
|
||||
|
||||
cur_burn.register(unit.unit_ps, "act_burn_rate", cur_burn.update)
|
||||
cur_burn.register(unit.unit_ps, "act_burn_rate", cur_burn.update)
|
||||
else
|
||||
lim.disable()
|
||||
end
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- unit statuses --
|
||||
-------------------
|
||||
|
||||
local stat_div = Div{parent=proc,width=38,height=19,x=57,y=6}
|
||||
local stat_div = Div{parent=proc,width=22,height=24,x=57,y=6}
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
local unit = units[i] ---@type ioctl_unit
|
||||
for i = 1, 4 do
|
||||
local tag_fg_bg = cpair(colors.gray, colors.white)
|
||||
local ind_fg_bg = cpair(colors.lightGray, colors.white)
|
||||
local ind_off = colors.lightGray
|
||||
|
||||
if i <= facility.num_units then
|
||||
tag_fg_bg = cpair(colors.black, colors.cyan)
|
||||
ind_fg_bg = bw_fg_bg
|
||||
ind_off = colors.gray
|
||||
end
|
||||
|
||||
local _y = ((i - 1) * 5) + 1
|
||||
|
||||
local unit_tag = Div{parent=stat_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)}
|
||||
local unit_tag = Div{parent=stat_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg}
|
||||
TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2}
|
||||
|
||||
local lights = Div{parent=stat_div,x=9,y=_y,width=12,height=4,fg_bg=bw_fg_bg}
|
||||
local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,colors.gray)}
|
||||
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local lights = Div{parent=stat_div,x=9,y=_y,width=14,height=4,fg_bg=ind_fg_bg}
|
||||
local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,ind_off)}
|
||||
local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,ind_off),flash=true,period=period.BLINK_250_MS}
|
||||
|
||||
ready.register(unit.unit_ps, "U_AutoReady", ready.update)
|
||||
degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update)
|
||||
if i <= facility.num_units then
|
||||
local unit = units[i] ---@type ioctl_unit
|
||||
|
||||
ready.register(unit.unit_ps, "U_AutoReady", ready.update)
|
||||
degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------
|
||||
@@ -195,7 +235,7 @@ local function new_view(root, x, y)
|
||||
-------------------------
|
||||
|
||||
local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" }
|
||||
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.purple,colors.black),radio_bg=colors.gray}
|
||||
local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.white,colors.black),radio_bg=colors.purple}
|
||||
|
||||
mode.register(facility.ps, "process_mode", mode.set_value)
|
||||
|
||||
@@ -261,6 +301,60 @@ local function new_view(root, x, y)
|
||||
for i = 1, #rate_limits do rate_limits[i].enable() end
|
||||
end
|
||||
end)
|
||||
|
||||
------------------------------
|
||||
-- waste production control --
|
||||
------------------------------
|
||||
|
||||
local waste_status = Div{parent=proc,width=24,height=4,x=57,y=1,}
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
local unit = units[i] ---@type ioctl_unit
|
||||
|
||||
TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8,height=1}
|
||||
local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=ind_wht}
|
||||
local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.waste.states_abbrv,value=1,min_width=6}
|
||||
|
||||
a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update)
|
||||
waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update)
|
||||
end
|
||||
|
||||
local waste_sel = Div{parent=proc,width=21,height=24,x=81,y=1}
|
||||
|
||||
TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=cpair(colors.black,colors.brown)}
|
||||
TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=TEXT_ALIGN.CENTER,width=21,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.brown)}
|
||||
|
||||
local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3}
|
||||
local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17}
|
||||
|
||||
status.register(facility.ps, "current_waste_product", status.update)
|
||||
|
||||
local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.white,colors.black),radio_bg=colors.brown}
|
||||
local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,colors.black)}
|
||||
|
||||
waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value)
|
||||
pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value)
|
||||
|
||||
local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=ind_wht}
|
||||
|
||||
fb_active.register(facility.ps, "pu_fallback_active", fb_active.update)
|
||||
|
||||
TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label}
|
||||
local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||
|
||||
TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label}
|
||||
local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||
|
||||
TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label}
|
||||
local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12d",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17}
|
||||
|
||||
pu_rate.register(facility.ps, "pu_rate", pu_rate.update)
|
||||
po_rate.register(facility.ps, "po_rate", po_rate.update)
|
||||
am_rate.register(facility.ps, "am_rate", am_rate.update)
|
||||
|
||||
local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17}
|
||||
|
||||
sna_count.register(facility.ps, "sna_count", sna_count.update)
|
||||
end
|
||||
|
||||
return new_view
|
||||
@@ -31,7 +31,7 @@ local function new_view(root, x, y, ps)
|
||||
local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg}
|
||||
|
||||
status.register(ps, "computed_status", status.update)
|
||||
prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end)
|
||||
prod_rate.register(ps, "steam_input_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end)
|
||||
flow_rate.register(ps, "flow_rate", flow_rate.update)
|
||||
|
||||
local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1}
|
||||
|
||||
@@ -31,30 +31,16 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
local period = core.flasher.PERIOD
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local lu_cpair = style.lu_colors
|
||||
local hzd_fg_bg = style.hzd_fg_bg
|
||||
|
||||
local waste_opts = {
|
||||
{
|
||||
text = "Auto",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.white, colors.gray)
|
||||
},
|
||||
{
|
||||
text = "Pu",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.green)
|
||||
},
|
||||
{
|
||||
text = "Po",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.cyan)
|
||||
},
|
||||
{
|
||||
text = "AM",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.purple)
|
||||
}
|
||||
}
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_yel = style.ind_yel
|
||||
local ind_red = style.ind_red
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
local period = core.flasher.PERIOD
|
||||
|
||||
-- create a unit view
|
||||
---@param parent graphics_element parent
|
||||
@@ -62,18 +48,17 @@ local waste_opts = {
|
||||
local function init(parent, id)
|
||||
local unit = iocontrol.get_db().units[id] ---@type ioctl_unit
|
||||
local f_ps = iocontrol.get_db().facility.ps
|
||||
|
||||
local main = Div{parent=parent,x=1,y=1}
|
||||
|
||||
if unit == nil then return main end
|
||||
|
||||
local u_ps = unit.unit_ps
|
||||
local b_ps = unit.boiler_ps_tbl
|
||||
local t_ps = unit.turbine_ps_tbl
|
||||
|
||||
local main = Div{parent=parent,x=1,y=1}
|
||||
|
||||
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
|
||||
local bw_fg_bg = cpair(colors.black, colors.white)
|
||||
local hzd_fg_bg = cpair(colors.white, colors.gray)
|
||||
local lu_cpair = cpair(colors.gray, colors.gray)
|
||||
|
||||
-----------------------------
|
||||
-- main stats and core map --
|
||||
-----------------------------
|
||||
@@ -160,7 +145,7 @@ local function init(parent, id)
|
||||
|
||||
-- connectivity
|
||||
local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)}
|
||||
local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)}
|
||||
local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=ind_wht}
|
||||
local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green}
|
||||
|
||||
plc_online.register(u_ps, "PLCOnline", plc_online.update)
|
||||
@@ -170,25 +155,25 @@ local function init(parent, id)
|
||||
annunciator.line_break()
|
||||
|
||||
-- operating state
|
||||
local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)}
|
||||
local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.white,colors.gray)}
|
||||
local r_active = IndicatorLight{parent=annunciator,label="Active",colors=ind_grn}
|
||||
local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=ind_wht}
|
||||
|
||||
r_active.register(u_ps, "status", r_active.update)
|
||||
r_auto.register(u_ps, "AutoControl", r_auto.update)
|
||||
|
||||
-- main unit transient/warning annunciator panel
|
||||
local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)}
|
||||
local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)}
|
||||
local r_ascrm = IndicatorLight{parent=annunciator,label="Auto Reactor SCRAM",colors=cpair(colors.red,colors.gray)}
|
||||
local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)}
|
||||
local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)}
|
||||
local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_hsrt = IndicatorLight{parent=annunciator,label="Startup Rate High",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=ind_red}
|
||||
local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=ind_red}
|
||||
local r_ascrm = IndicatorLight{parent=annunciator,label="Auto Reactor SCRAM",colors=ind_red}
|
||||
local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=ind_yel}
|
||||
local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=ind_red}
|
||||
local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=ind_yel}
|
||||
local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=ind_yel}
|
||||
local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=ind_red}
|
||||
local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=ind_yel}
|
||||
local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=ind_yel}
|
||||
local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=ind_yel}
|
||||
local r_hsrt = IndicatorLight{parent=annunciator,label="Startup Rate High",colors=ind_yel}
|
||||
|
||||
r_scram.register(u_ps, "ReactorSCRAM", r_scram.update)
|
||||
r_mscrm.register(u_ps, "ManualReactorSCRAM", r_mscrm.update)
|
||||
@@ -209,15 +194,15 @@ local function init(parent, id)
|
||||
local rps = Rectangle{parent=main,border=border(1,colors.cyan,true),thin=true,width=33,height=12,x=46,y=9}
|
||||
local rps_annunc = Div{parent=rps,width=31,height=10,x=2,y=1}
|
||||
|
||||
local rps_trp = IndicatorLight{parent=rps_annunc,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage Level High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local rps_exh = IndicatorLight{parent=rps_annunc,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_exw = IndicatorLight{parent=rps_annunc,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_tmp = IndicatorLight{parent=rps_annunc,label="Core Temperature High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local rps_nof = IndicatorLight{parent=rps_annunc,label="No Fuel",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_loc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local rps_trp = IndicatorLight{parent=rps_annunc,label="RPS Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
local rps_dmg = IndicatorLight{parent=rps_annunc,label="Damage Level High",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
local rps_exh = IndicatorLight{parent=rps_annunc,label="Excess Heated Coolant",colors=ind_yel}
|
||||
local rps_exw = IndicatorLight{parent=rps_annunc,label="Excess Waste",colors=ind_yel}
|
||||
local rps_tmp = IndicatorLight{parent=rps_annunc,label="Core Temperature High",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
local rps_nof = IndicatorLight{parent=rps_annunc,label="No Fuel",colors=ind_yel}
|
||||
local rps_loc = IndicatorLight{parent=rps_annunc,label="Coolant Level Low Low",colors=ind_yel}
|
||||
local rps_flt = IndicatorLight{parent=rps_annunc,label="PPM Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||
local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=ind_yel,flash=true,period=period.BLINK_500_MS}
|
||||
local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
|
||||
rps_trp.register(u_ps, "rps_tripped", rps_trp.update)
|
||||
@@ -238,12 +223,12 @@ local function init(parent, id)
|
||||
local rcs_annunc = Div{parent=rcs,width=27,height=22,x=3,y=1}
|
||||
local rcs_tags = Div{parent=rcs,width=2,height=16,x=1,y=7}
|
||||
|
||||
local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)}
|
||||
local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=ind_yel}
|
||||
local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.green}
|
||||
local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)}
|
||||
local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)}
|
||||
local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)}
|
||||
local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)}
|
||||
local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=ind_yel}
|
||||
local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=ind_yel}
|
||||
local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=ind_yel}
|
||||
local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=ind_yel}
|
||||
|
||||
c_flt.register(u_ps, "RCSFault", c_flt.update)
|
||||
c_emg.register(u_ps, "EmergencyCoolant", c_emg.update)
|
||||
@@ -266,11 +251,11 @@ local function init(parent, id)
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)}
|
||||
local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||
b1_wll.register(b_ps[1], "WasterLevelLow", b1_wll.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||
b1_hr.register(b_ps[1], "HeatingRateLow", b1_hr.update)
|
||||
end
|
||||
if unit.num_boilers > 1 then
|
||||
@@ -282,11 +267,11 @@ local function init(parent, id)
|
||||
end
|
||||
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)}
|
||||
local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||
b2_wll.register(b_ps[2], "WasterLevelLow", b2_wll.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||
b2_hr.register(b_ps[2], "HeatingRateLow", b2_hr.update)
|
||||
end
|
||||
|
||||
@@ -299,15 +284,15 @@ local function init(parent, id)
|
||||
t1_sdo.register(t_ps[1], "SteamDumpOpen", t1_sdo.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||
local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||
t1_tos.register(t_ps[1], "TurbineOverSpeed", t1_tos.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||
t1_gtrp.register(t_ps[1], "GeneratorTrip", t1_gtrp.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
t1_trp.register(t_ps[1], "TurbineTrip", t1_trp.update)
|
||||
|
||||
if unit.num_turbines > 1 then
|
||||
@@ -320,15 +305,15 @@ local function init(parent, id)
|
||||
t2_sdo.register(t_ps[2], "SteamDumpOpen", t2_sdo.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||
local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||
t2_tos.register(t_ps[2], "TurbineOverSpeed", t2_tos.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||
t2_gtrp.register(t_ps[2], "GeneratorTrip", t2_gtrp.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
t2_trp.register(t_ps[2], "TurbineTrip", t2_trp.update)
|
||||
end
|
||||
|
||||
@@ -340,15 +325,15 @@ local function init(parent, id)
|
||||
t3_sdo.register(t_ps[3], "SteamDumpOpen", t3_sdo.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||
local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=ind_red}
|
||||
t3_tos.register(t_ps[3], "TurbineOverSpeed", t3_tos.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=ind_yel,flash=true,period=period.BLINK_250_MS}
|
||||
t3_gtrp.register(t_ps[3], "GeneratorTrip", t3_gtrp.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=ind_red,flash=true,period=period.BLINK_250_MS}
|
||||
t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update)
|
||||
end
|
||||
|
||||
@@ -363,7 +348,7 @@ local function init(parent, id)
|
||||
TextBox{parent=burn_control,x=9,y=2,text="mB/t"}
|
||||
|
||||
local set_burn = function () unit.set_burn(burn_rate.get_value()) end
|
||||
local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=dis_colors,callback=set_burn}
|
||||
local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=style.wh_gray,dis_fg_bg=dis_colors,callback=set_burn}
|
||||
|
||||
burn_rate.register(u_ps, "burn_rate", burn_rate.set_value)
|
||||
burn_rate.register(u_ps, "max_burn", burn_rate.set_max)
|
||||
@@ -398,7 +383,7 @@ local function init(parent, id)
|
||||
local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49}
|
||||
local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1}
|
||||
|
||||
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6}
|
||||
local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.waste.unit_opts,callback=unit.set_waste,min_width=6}
|
||||
|
||||
waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value)
|
||||
|
||||
@@ -495,7 +480,7 @@ local function init(parent, id)
|
||||
auto_div.line_break()
|
||||
|
||||
local function set_group() unit.set_group(group.get_value() - 1) end
|
||||
local set_grp_btn = PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.white),callback=set_group}
|
||||
local set_grp_btn = PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=style.wh_gray,dis_fg_bg=cpair(colors.gray,colors.white),callback=set_group}
|
||||
|
||||
auto_div.line_break()
|
||||
|
||||
@@ -506,8 +491,8 @@ local function init(parent, id)
|
||||
|
||||
auto_div.line_break()
|
||||
|
||||
local a_rdy = IndicatorLight{parent=auto_div,label="Ready",x=2,colors=cpair(colors.green,colors.gray)}
|
||||
local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_1000_MS}
|
||||
local a_rdy = IndicatorLight{parent=auto_div,label="Ready",x=2,colors=ind_grn}
|
||||
local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=ind_wht,flash=true,period=period.BLINK_1000_MS}
|
||||
|
||||
a_rdy.register(u_ps, "U_AutoReady", a_rdy.update)
|
||||
|
||||
|
||||
225
coordinator/ui/components/unit_flow.lua
Normal file
225
coordinator/ui/components/unit_flow.lua
Normal file
@@ -0,0 +1,225 @@
|
||||
--
|
||||
-- Basic Unit Flow Overview
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local PipeNetwork = require("graphics.elements.pipenet")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local Rectangle = require("graphics.elements.rectangle")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
|
||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
local sprintf = util.sprintf
|
||||
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
local pipe = core.pipe
|
||||
|
||||
local wh_gray = style.wh_gray
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local text_c = style.text_colors
|
||||
local lu_c = style.lu_colors
|
||||
|
||||
local ind_grn = style.ind_grn
|
||||
local ind_wht = style.ind_wht
|
||||
|
||||
-- make a new unit flow window
|
||||
---@param parent graphics_element parent
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
---@param wide boolean whether to render wide version
|
||||
---@param unit ioctl_unit unit database entry
|
||||
local function make(parent, x, y, wide, unit)
|
||||
local height = 16
|
||||
|
||||
local v_start = 1 + ((unit.unit_id - 1) * 5)
|
||||
local prv_start = 1 + ((unit.unit_id - 1) * 3)
|
||||
local v_fields = { "pu", "po", "pl", "am" }
|
||||
local v_names = {
|
||||
sprintf("PV%02d-PU", v_start),
|
||||
sprintf("PV%02d-PO", v_start + 1),
|
||||
sprintf("PV%02d-PL", v_start + 2),
|
||||
sprintf("PV%02d-AM", v_start + 3),
|
||||
sprintf("PRV%02d", prv_start),
|
||||
sprintf("PRV%02d", prv_start + 1),
|
||||
sprintf("PRV%02d", prv_start + 2)
|
||||
}
|
||||
|
||||
assert(parent.get_height() >= (y + height), "flow display not of sufficient vertical resolution (add an additional row of monitors) " .. y .. "," .. parent.get_height())
|
||||
|
||||
local function _wide(a, b) return util.trinary(wide, a, b) end
|
||||
|
||||
-- bounding box div
|
||||
local root = Div{parent=parent,x=x,y=y,width=_wide(136, 114),height=height}
|
||||
|
||||
local lg_gray = cpair(colors.lightGray, colors.gray)
|
||||
|
||||
------------------
|
||||
-- COOLING LOOP --
|
||||
------------------
|
||||
|
||||
local reactor = Rectangle{parent=root,x=1,y=1,border=border(1,colors.gray,true),width=19,height=5,fg_bg=wh_gray}
|
||||
TextBox{parent=reactor,y=1,text="FISSION REACTOR",alignment=TEXT_ALIGN.CENTER,height=1}
|
||||
TextBox{parent=reactor,y=3,text="UNIT #"..unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1}
|
||||
TextBox{parent=root,x=19,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||
TextBox{parent=root,x=3,y=5,text="\x19",width=1,height=1,fg_bg=lg_gray}
|
||||
|
||||
local rc_pipes = {}
|
||||
|
||||
local emc_x = 42 -- emergency coolant connection x point
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
table.insert(rc_pipes, pipe(0, 1, _wide(28, 19), 1, colors.lightBlue, true))
|
||||
table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true))
|
||||
table.insert(rc_pipes, pipe(_wide(46 ,39), 1, _wide(72,58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(_wide(46,39), 3, _wide(72,58), 3, colors.white, true))
|
||||
else
|
||||
emc_x = 3
|
||||
table.insert(rc_pipes, pipe(0, 1, _wide(72,58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(0, 3, _wide(72,58), 3, colors.white, true))
|
||||
end
|
||||
|
||||
if unit.has_tank then
|
||||
table.insert(rc_pipes, pipe(emc_x, 1, emc_x, 0, colors.blue, true, true))
|
||||
end
|
||||
|
||||
local prv_yo = math.max(3 - unit.num_turbines, 0)
|
||||
for i = 1, unit.num_turbines do
|
||||
local py = 2 * (i - 1) + prv_yo
|
||||
table.insert(rc_pipes, pipe(_wide(92, 78), py, _wide(104, 83), py, colors.white, true))
|
||||
end
|
||||
|
||||
PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=colors.lightGray}
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
local cc_rate = DataIndicator{parent=root,x=_wide(25,22),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local hc_rate = DataIndicator{parent=root,x=_wide(25,22),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
|
||||
cc_rate.register(unit.unit_ps, "boiler_boil_sum", function (sum) cc_rate.update(sum * 10) end)
|
||||
hc_rate.register(unit.unit_ps, "heating_rate", hc_rate.update)
|
||||
|
||||
local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray}
|
||||
TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=TEXT_ALIGN.CENTER,height=1}
|
||||
TextBox{parent=boiler,y=3,text="BOILERS",alignment=TEXT_ALIGN.CENTER,height=1}
|
||||
TextBox{parent=root,x=_wide(47,40),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||
TextBox{parent=root,x=_wide(65,58),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||
|
||||
local wt_rate = DataIndicator{parent=root,x=_wide(71,61),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local st_rate = DataIndicator{parent=root,x=_wide(71,61),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
|
||||
wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update)
|
||||
st_rate.register(unit.unit_ps, "boiler_boil_sum", st_rate.update)
|
||||
else
|
||||
local wt_rate = DataIndicator{parent=root,x=28,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
local st_rate = DataIndicator{parent=root,x=28,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=bw_fg_bg}
|
||||
|
||||
wt_rate.register(unit.unit_ps, "turbine_flow_sum", wt_rate.update)
|
||||
st_rate.register(unit.unit_ps, "heating_rate", st_rate.update)
|
||||
end
|
||||
|
||||
local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray}
|
||||
TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=TEXT_ALIGN.CENTER,height=1}
|
||||
TextBox{parent=turbine,y=3,text=util.trinary(unit.num_turbines>1,"GENERATORS","GENERATOR"),alignment=TEXT_ALIGN.CENTER,height=1}
|
||||
TextBox{parent=root,x=_wide(93,79),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray}
|
||||
|
||||
for i = 1, unit.num_turbines do
|
||||
local ry = 1 + (2 * (i - 1)) + prv_yo
|
||||
TextBox{parent=root,x=_wide(125,103),y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3,height=1}
|
||||
local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=colors.gray,c2=colors.yellow,c3=colors.red}
|
||||
state.register(unit.turbine_ps_tbl[i], "SteamDumpOpen", state.update)
|
||||
end
|
||||
|
||||
----------------------
|
||||
-- WASTE PROCESSING --
|
||||
----------------------
|
||||
|
||||
local waste = Div{parent=root,x=3,y=6}
|
||||
|
||||
local waste_pipes = {
|
||||
pipe(0, 0, _wide(19, 16), 1, colors.brown, true),
|
||||
pipe(_wide(14, 13), 1, _wide(19, 17), 5, colors.brown, true),
|
||||
pipe(_wide(22, 19), 1, _wide(49, 45), 1, colors.brown, true),
|
||||
pipe(_wide(22, 19), 5, _wide(28, 24), 5, colors.brown, true),
|
||||
|
||||
pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.green, true),
|
||||
|
||||
pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.cyan, true),
|
||||
pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.cyan, true),
|
||||
pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true),
|
||||
pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true),
|
||||
|
||||
pipe(_wide(108, 94), 1, _wide(132, 110), 6, colors.black, true, true),
|
||||
pipe(_wide(108, 94), 4, _wide(111, 95), 1, colors.black, true, true),
|
||||
pipe(_wide(132, 110), 6, _wide(130, 108), 6, colors.black, true, true)
|
||||
}
|
||||
|
||||
PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=colors.lightGray}
|
||||
|
||||
local function _valve(vx, vy, n)
|
||||
TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=text_c,width=2,height=1}
|
||||
local conn = IndicatorLight{parent=waste,x=vx-3,y=vy+1,label=v_names[n],colors=ind_grn}
|
||||
local open = IndicatorLight{parent=waste,x=vx-3,y=vy+2,label="OPEN",colors=ind_wht}
|
||||
conn.register(unit.unit_ps, util.c("V_", v_fields[n], "_conn"), conn.update)
|
||||
open.register(unit.unit_ps, util.c("V_", v_fields[n], "_state"), open.update)
|
||||
end
|
||||
|
||||
local function _machine(mx, my, name)
|
||||
local l = string.len(name) + 2
|
||||
TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=TEXT_ALIGN.CENTER,fg_bg=lg_gray,width=l,height=1}
|
||||
TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=TEXT_ALIGN.CENTER,fg_bg=wh_gray,width=l,height=1}
|
||||
end
|
||||
|
||||
local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local pu_rate = DataIndicator{parent=waste,x=_wide(82,70),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
local spent_rate = DataIndicator{parent=waste,x=_wide(117,99),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=bw_fg_bg}
|
||||
|
||||
waste_rate.register(unit.unit_ps, "act_burn_rate", waste_rate.update)
|
||||
pu_rate.register(unit.unit_ps, "pu_rate", pu_rate.update)
|
||||
po_rate.register(unit.unit_ps, "po_rate", po_rate.update)
|
||||
popl_rate.register(unit.unit_ps, "po_pl_rate", popl_rate.update)
|
||||
poam_rate.register(unit.unit_ps, "po_am_rate", poam_rate.update)
|
||||
spent_rate.register(unit.unit_ps, "ws_rate", spent_rate.update)
|
||||
|
||||
_valve(_wide(21, 18), 2, 1)
|
||||
_valve(_wide(21, 18), 6, 2)
|
||||
_valve(_wide(73, 62), 5, 3)
|
||||
_valve(_wide(73, 62), 9, 4)
|
||||
|
||||
_machine(_wide(51, 45), 1, "CENTRIFUGE \x1a");
|
||||
_machine(_wide(97, 83), 1, "PRC [Pu] \x1a");
|
||||
_machine(_wide(97, 83), 4, "PRC [Po] \x1a");
|
||||
_machine(_wide(116, 94), 6, "SPENT WASTE \x1b")
|
||||
|
||||
TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=TEXT_ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
||||
local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=bw_fg_bg}
|
||||
local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn}
|
||||
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c,label="CNT",unit="",format="%2d",value=0,width=7}
|
||||
local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17}
|
||||
local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17}
|
||||
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c,label="IN",unit="mB/t",format="%9.2f",value=0,width=17}
|
||||
|
||||
sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end)
|
||||
sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update)
|
||||
sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update)
|
||||
sna_max.register(unit.unit_ps, "sna_prod_rate", sna_max.update)
|
||||
sna_in.register(unit.unit_ps, "sna_in", sna_in.update)
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
return make
|
||||
381
coordinator/ui/layout/flow_view.lua
Normal file
381
coordinator/ui/layout/flow_view.lua
Normal file
@@ -0,0 +1,381 @@
|
||||
--
|
||||
-- Flow Monitor GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
|
||||
local unit_flow = require("coordinator.ui.components.unit_flow")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local PipeNetwork = require("graphics.elements.pipenet")
|
||||
local Rectangle = require("graphics.elements.rectangle")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
local HorizontalBar = require("graphics.elements.indicators.hbar")
|
||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||
local StateIndicator = require("graphics.elements.indicators.state")
|
||||
|
||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
local pipe = core.pipe
|
||||
|
||||
local wh_gray = style.wh_gray
|
||||
local bw_fg_bg = style.bw_fg_bg
|
||||
local text_col = style.text_colors
|
||||
local lu_col = style.lu_colors
|
||||
|
||||
-- create new flow view
|
||||
---@param main graphics_element main displaybox
|
||||
local function init(main)
|
||||
local facility = iocontrol.get_db().facility
|
||||
local units = iocontrol.get_db().units
|
||||
|
||||
local tank_defs = facility.tank_defs
|
||||
local tank_list = facility.tank_list
|
||||
|
||||
-- window header message
|
||||
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
|
||||
|
||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||
|
||||
local po_pipes = {}
|
||||
local water_pipes = {}
|
||||
|
||||
-- get the y offset for this unit index
|
||||
---@param idx integer unit index
|
||||
local function y_ofs(idx) return ((idx - 1) * 20) end
|
||||
|
||||
-- determinte facility tank start/end from the definitions list
|
||||
---@param start_idx integer start index of table iteration
|
||||
---@param end_idx integer end index of table iteration
|
||||
local function find_fdef(start_idx, end_idx)
|
||||
local first, last = 4, 0
|
||||
for i = start_idx, end_idx do
|
||||
if tank_defs[i] == 2 then
|
||||
last = i
|
||||
if i < first then first = i end
|
||||
end
|
||||
end
|
||||
return first, last
|
||||
end
|
||||
|
||||
if facility.tank_mode == 0 or facility.tank_mode == 8 then
|
||||
-- (0) tanks belong to reactor units OR (8) 4 total facility tanks (A B C D)
|
||||
for i = 1, facility.num_units do
|
||||
if units[i].has_tank then
|
||||
local y = y_ofs(i)
|
||||
table.insert(water_pipes, pipe(2, y, 2, y + 3, colors.blue, true))
|
||||
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
|
||||
|
||||
local u = units[i] ---@type ioctl_unit
|
||||
local x = util.trinary(u.num_boilers == 0, 45, 84)
|
||||
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
|
||||
end
|
||||
end
|
||||
else
|
||||
-- setup connections for units with emergency coolant, always the same
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] > 0 then
|
||||
local y = y_ofs(i)
|
||||
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(1, y, 21, y, colors.blue, true))
|
||||
else
|
||||
table.insert(water_pipes, pipe(2, y, 2, y + 3, colors.blue, true))
|
||||
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
|
||||
end
|
||||
|
||||
local u = units[i] ---@type ioctl_unit
|
||||
local x = util.trinary(u.num_boilers == 0, 45, 84)
|
||||
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
|
||||
end
|
||||
end
|
||||
|
||||
if facility.tank_mode == 1 then
|
||||
-- (1) 1 total facility tank (A A A A)
|
||||
local first_fdef, last_fdef = find_fdef(1, #tank_defs)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
if i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif facility.tank_mode == 2 then
|
||||
-- (2) 2 total facility tanks (A A A B)
|
||||
local first_fdef, last_fdef = find_fdef(1, math.min(3, #tank_defs))
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
if i == 4 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif facility.tank_mode == 3 then
|
||||
-- (3) 2 total facility tanks (A A B B)
|
||||
for _, a in pairs({ 1, 3 }) do
|
||||
local b = a + 1
|
||||
if tank_defs[a] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y_ofs(a), 1, y_ofs(a) + 6, colors.blue, true))
|
||||
if tank_defs[b] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y_ofs(b) - 13, 1, y_ofs(b), colors.blue, true))
|
||||
end
|
||||
elseif tank_defs[b] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y_ofs(b), 1, y_ofs(b) + 6, colors.blue, true))
|
||||
end
|
||||
end
|
||||
elseif facility.tank_mode == 4 then
|
||||
-- (4) 2 total facility tanks (A B B B)
|
||||
local first_fdef, last_fdef = find_fdef(2, #tank_defs)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
if i == 1 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif facility.tank_mode == 5 then
|
||||
-- (5) 3 total facility tanks (A A B C)
|
||||
local first_fdef, last_fdef = find_fdef(1, math.min(2, #tank_defs))
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
if i == 3 or i == 4 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif facility.tank_mode == 6 then
|
||||
-- (6) 3 total facility tanks (A B B C)
|
||||
local first_fdef, last_fdef = find_fdef(2, math.min(3, #tank_defs))
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
if i == 1 or i == 4 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif facility.tank_mode == 7 then
|
||||
-- (7) 3 total facility tanks (A B C C)
|
||||
local first_fdef, last_fdef = find_fdef(3, #tank_defs)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
if i == 1 or i == 2 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local flow_x = 3
|
||||
if #water_pipes > 0 then
|
||||
flow_x = 25
|
||||
PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=colors.lightGray}
|
||||
end
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
local y_offset = y_ofs(i)
|
||||
unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i])
|
||||
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true))
|
||||
end
|
||||
|
||||
PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=colors.lightGray}
|
||||
|
||||
-----------------
|
||||
-- tank valves --
|
||||
-----------------
|
||||
|
||||
local next_f_id = 1
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] > 0 then
|
||||
local vy = 3 + y_ofs(i)
|
||||
|
||||
TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=cpair(colors.black,colors.lightGray),width=2,height=1}
|
||||
|
||||
local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", i * 5),colors=style.ind_grn}
|
||||
local open = IndicatorLight{parent=main,x=9,y=vy+2,label="OPEN",colors=style.ind_wht}
|
||||
|
||||
conn.register(units[i].unit_ps, "V_emc_conn", conn.update)
|
||||
open.register(units[i].unit_ps, "V_emc_state", open.update)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- dynamic tanks --
|
||||
-------------------
|
||||
|
||||
for i = 1, #tank_list do
|
||||
if tank_list[i] > 0 then
|
||||
local id = "U-" .. i
|
||||
local f_id = next_f_id
|
||||
if tank_list[i] == 2 then
|
||||
id = "F-" .. next_f_id
|
||||
next_f_id = next_f_id + 1
|
||||
end
|
||||
|
||||
local y_offset = y_ofs(i)
|
||||
|
||||
local tank = Div{parent=main,x=3,y=7+y_offset,width=20,height=14}
|
||||
|
||||
TextBox{parent=tank,text=" ",height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)}
|
||||
TextBox{parent=tank,text="DYNAMIC TANK "..id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.wh_gray}
|
||||
|
||||
local tank_box = Rectangle{parent=tank,border=border(1,colors.gray,true),width=20,height=12}
|
||||
|
||||
local status = StateIndicator{parent=tank_box,x=3,y=1,states=style.dtank.states,value=1,min_width=14}
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=3,text="Fill",height=1,width=10,fg_bg=style.label}
|
||||
local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col}
|
||||
local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,unit="mB",lu_colors=lu_col,width=16,fg_bg=bw_fg_bg}
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=6,text="Water Level",height=1,width=11,fg_bg=style.label}
|
||||
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16}
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=9,text="In/Out Mode",height=1,width=11,fg_bg=style.label}
|
||||
local can_fill = IndicatorLight{parent=tank_box,x=2,y=10,label="FILL",colors=style.ind_wht}
|
||||
local can_empty = IndicatorLight{parent=tank_box,x=10,y=10,label="EMPTY",colors=style.ind_wht}
|
||||
|
||||
local function _can_fill(mode)
|
||||
can_fill.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.FILL))
|
||||
end
|
||||
|
||||
local function _can_empty(mode)
|
||||
can_empty.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.EMPTY))
|
||||
end
|
||||
|
||||
if tank_list[i] == 1 then
|
||||
status.register(units[i].tank_ps_tbl[1], "computed_status", status.update)
|
||||
tank_pcnt.register(units[i].tank_ps_tbl[1], "fill", function (f) tank_pcnt.update(f * 100) end)
|
||||
tank_amnt.register(units[i].tank_ps_tbl[1], "stored", function (sto) tank_amnt.update(sto.amount) end)
|
||||
level.register(units[i].tank_ps_tbl[1], "fill", level.update)
|
||||
can_fill.register(units[i].tank_ps_tbl[1], "container_mode", _can_fill)
|
||||
can_empty.register(units[i].tank_ps_tbl[1], "container_mode", _can_empty)
|
||||
else
|
||||
status.register(facility.tank_ps_tbl[f_id], "computed_status", status.update)
|
||||
tank_pcnt.register(facility.tank_ps_tbl[f_id], "fill", function (f) tank_pcnt.update(f * 100) end)
|
||||
tank_amnt.register(facility.tank_ps_tbl[f_id], "stored", function (sto) tank_amnt.update(sto.amount) end)
|
||||
level.register(facility.tank_ps_tbl[f_id], "fill", level.update)
|
||||
can_fill.register(facility.tank_ps_tbl[f_id], "container_mode", _can_fill)
|
||||
can_empty.register(facility.tank_ps_tbl[f_id], "container_mode", _can_empty)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---------
|
||||
-- SPS --
|
||||
---------
|
||||
|
||||
local sps = Div{parent=main,x=140,y=3,height=12}
|
||||
|
||||
TextBox{parent=sps,text=" ",width=24,height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)}
|
||||
TextBox{parent=sps,text="SPS",alignment=TEXT_ALIGN.CENTER,width=24,height=1,fg_bg=wh_gray}
|
||||
|
||||
local sps_box = Rectangle{parent=sps,border=border(1,colors.gray,true),width=24,height=10}
|
||||
|
||||
local status = StateIndicator{parent=sps_box,x=5,y=1,states=style.sps.states,value=1,min_width=14}
|
||||
|
||||
status.register(facility.sps_ps_tbl[1], "computed_status", status.update)
|
||||
|
||||
TextBox{parent=sps_box,x=2,y=3,text="Input Rate",height=1,width=10,fg_bg=style.label}
|
||||
local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.3f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg}
|
||||
|
||||
sps_in.register(facility.ps, "po_am_rate", sps_in.update)
|
||||
|
||||
TextBox{parent=sps_box,x=2,y=6,text="Production Rate",height=1,width=15,fg_bg=style.label}
|
||||
local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg}
|
||||
|
||||
sps_rate.register(facility.sps_ps_tbl[1], "process_rate", function (r) sps_rate.update(r * 1000) end)
|
||||
|
||||
----------------
|
||||
-- statistics --
|
||||
----------------
|
||||
|
||||
TextBox{parent=main,x=145,y=16,text="PROC. WASTE",alignment=TEXT_ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
||||
local pr_waste = Rectangle{parent=main,x=145,y=17,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=bw_fg_bg}
|
||||
local pu = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17}
|
||||
local po = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Po",unit="mB/t",format="%9.3f",value=0,width=17}
|
||||
local popl = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="PoPl",unit="mB/t",format="%7.3f",value=0,width=17}
|
||||
|
||||
pu.register(facility.ps, "pu_rate", pu.update)
|
||||
po.register(facility.ps, "po_rate", po.update)
|
||||
popl.register(facility.ps, "po_pl_rate", popl.update)
|
||||
|
||||
TextBox{parent=main,x=145,y=23,text="SPENT WASTE",alignment=TEXT_ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray}
|
||||
local sp_waste = Rectangle{parent=main,x=145,y=24,border=border(1,colors.gray,true),width=19,height=3,thin=true,fg_bg=bw_fg_bg}
|
||||
local sum_sp_waste = DataIndicator{parent=sp_waste,lu_colors=lu_col,label="SUM",unit="mB/t",format="%8.3f",value=0,width=17}
|
||||
|
||||
sum_sp_waste.register(facility.ps, "spent_waste_rate", sum_sp_waste.update)
|
||||
end
|
||||
|
||||
return init
|
||||
124
coordinator/ui/layout/front_panel.lua
Normal file
124
coordinator/ui/layout/front_panel.lua
Normal file
@@ -0,0 +1,124 @@
|
||||
--
|
||||
-- Coordinator Front Panel GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local pgi = require("coordinator.ui.pgi")
|
||||
local style = require("coordinator.ui.style")
|
||||
|
||||
local pkt_entry = require("coordinator.ui.components.pkt_entry")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local ListBox = require("graphics.elements.listbox")
|
||||
local MultiPane = require("graphics.elements.multipane")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local TabBar = require("graphics.elements.controls.tabbar")
|
||||
|
||||
local LED = require("graphics.elements.indicators.led")
|
||||
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- create new front panel view
|
||||
---@param panel graphics_element main displaybox
|
||||
---@param num_units integer number of units (number of unit monitors)
|
||||
local function init(panel, num_units)
|
||||
local ps = iocontrol.get_db().fp.ps
|
||||
|
||||
TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.fp.header}
|
||||
|
||||
local page_div = Div{parent=panel,x=1,y=3}
|
||||
|
||||
--
|
||||
-- system indicators
|
||||
--
|
||||
|
||||
local main_page = Div{parent=page_div,x=1,y=1}
|
||||
|
||||
local system = Div{parent=main_page,width=14,height=17,x=2,y=2}
|
||||
|
||||
local status = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
|
||||
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
|
||||
status.update(true)
|
||||
system.line_break()
|
||||
|
||||
heartbeat.register(ps, "heartbeat", heartbeat.update)
|
||||
|
||||
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
system.line_break()
|
||||
|
||||
modem.register(ps, "has_modem", modem.update)
|
||||
network.register(ps, "link_state", network.update)
|
||||
|
||||
local speaker = LED{parent=system,label="SPEAKER",colors=cpair(colors.green,colors.green_off)}
|
||||
speaker.register(ps, "has_speaker", speaker.update)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
|
||||
local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2}
|
||||
|
||||
local main_monitor = LED{parent=monitors,label="MAIN MONITOR",colors=cpair(colors.green,colors.green_off)}
|
||||
main_monitor.register(ps, "main_monitor", main_monitor.update)
|
||||
|
||||
local flow_monitor = LED{parent=monitors,label="FLOW MONITOR",colors=cpair(colors.green,colors.green_off)}
|
||||
flow_monitor.register(ps, "flow_monitor", flow_monitor.update)
|
||||
|
||||
monitors.line_break()
|
||||
|
||||
for i = 1, num_units do
|
||||
local unit_monitor = LED{parent=monitors,label="UNIT "..i.." MONITOR",colors=cpair(colors.green,colors.green_off)}
|
||||
unit_monitor.register(ps, "unit_monitor_" .. i, unit_monitor.update)
|
||||
end
|
||||
|
||||
--
|
||||
-- about footer
|
||||
--
|
||||
|
||||
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
|
||||
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
|
||||
|
||||
fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||
comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||
|
||||
--
|
||||
-- page handling
|
||||
--
|
||||
|
||||
-- API page
|
||||
|
||||
local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
|
||||
local _ = Div{parent=api_list,height=1,hidden=true} -- padding
|
||||
|
||||
-- assemble page panes
|
||||
|
||||
local panes = { main_page, api_page }
|
||||
|
||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
|
||||
local tabs = {
|
||||
{ name = "CRD", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "API", color = cpair(colors.black, colors.ivory) },
|
||||
}
|
||||
|
||||
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=cpair(colors.black,colors.white)}
|
||||
|
||||
-- link pocket API list management to PGI
|
||||
pgi.link_elements(api_list, pkt_entry)
|
||||
end
|
||||
|
||||
return init
|
||||
@@ -9,7 +9,7 @@ local iocontrol = require("coordinator.iocontrol")
|
||||
local style = require("coordinator.ui.style")
|
||||
|
||||
local imatrix = require("coordinator.ui.components.imatrix")
|
||||
local process_ctl = require("coordinator.ui.components.processctl")
|
||||
local process_ctl = require("coordinator.ui.components.process_ctl")
|
||||
local unit_overview = require("coordinator.ui.components.unit_overview")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
60
coordinator/ui/pgi.lua
Normal file
60
coordinator/ui/pgi.lua
Normal file
@@ -0,0 +1,60 @@
|
||||
--
|
||||
-- Protected Graphics Interface
|
||||
--
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local pgi = {}
|
||||
|
||||
local data = {
|
||||
pkt_list = nil, ---@type nil|graphics_element
|
||||
pkt_entry = nil, ---@type function
|
||||
-- session entries
|
||||
s_entries = { pkt = {} }
|
||||
}
|
||||
|
||||
-- link list boxes
|
||||
---@param pkt_list graphics_element pocket list element
|
||||
---@param pkt_entry function pocket entry constructor
|
||||
function pgi.link_elements(pkt_list, pkt_entry)
|
||||
data.pkt_list = pkt_list
|
||||
data.pkt_entry = pkt_entry
|
||||
end
|
||||
|
||||
-- unlink all fields, disabling the PGI
|
||||
function pgi.unlink()
|
||||
data.pkt_list = nil
|
||||
data.pkt_entry = nil
|
||||
end
|
||||
|
||||
-- add a PKT entry to the PKT list
|
||||
---@param session_id integer pocket session
|
||||
function pgi.create_pkt_entry(session_id)
|
||||
if data.pkt_list ~= nil and data.pkt_entry ~= nil then
|
||||
local success, result = pcall(data.pkt_entry, data.pkt_list, session_id)
|
||||
|
||||
if success then
|
||||
data.s_entries.pkt[session_id] = result
|
||||
else
|
||||
log.error(util.c("PGI: failed to create PKT entry (", result, ")"), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- delete a PKT entry from the PKT list
|
||||
---@param session_id integer pocket session
|
||||
function pgi.delete_pkt_entry(session_id)
|
||||
if data.s_entries.pkt[session_id] ~= nil then
|
||||
local success, result = pcall(data.s_entries.pkt[session_id].delete)
|
||||
data.s_entries.pkt[session_id] = nil
|
||||
|
||||
if not success then
|
||||
log.error(util.c("PGI: failed to delete PKT entry (", result, ")"), true)
|
||||
end
|
||||
else
|
||||
log.debug(util.c("PGI: tried to delete unknown PKT entry ", session_id))
|
||||
end
|
||||
end
|
||||
|
||||
return pgi
|
||||
@@ -10,6 +10,41 @@ local cpair = core.cpair
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
-- add color mappings for front panel
|
||||
colors.ivory = colors.pink
|
||||
colors.yellow_hc = colors.purple
|
||||
colors.red_off = colors.brown
|
||||
colors.yellow_off = colors.magenta
|
||||
colors.green_off = colors.lime
|
||||
|
||||
-- front panel styling
|
||||
|
||||
style.fp = {}
|
||||
|
||||
style.fp.root = cpair(colors.black, colors.ivory)
|
||||
style.fp.header = cpair(colors.black, colors.lightGray)
|
||||
|
||||
style.fp.colors = {
|
||||
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
|
||||
{ c = colors.orange, hex = 0xffb659 },
|
||||
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
|
||||
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
|
||||
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||
-- { c = colors.white, hex = 0xdcd9ca },
|
||||
{ c = colors.lightGray, hex = 0xb1b8b3 },
|
||||
{ c = colors.gray, hex = 0x575757 },
|
||||
-- { c = colors.black, hex = 0x191919 },
|
||||
{ c = colors.brown, hex = 0x672223 } -- RED OFF
|
||||
}
|
||||
|
||||
-- main GUI styling
|
||||
|
||||
style.root = cpair(colors.black, colors.lightGray)
|
||||
style.header = cpair(colors.white, colors.gray)
|
||||
style.label = cpair(colors.gray, colors.lightGray)
|
||||
@@ -33,7 +68,22 @@ style.colors = {
|
||||
-- { c = colors.brown, hex = 0x7f664c }
|
||||
}
|
||||
|
||||
-- MAIN LAYOUT --
|
||||
-- COMMON COLOR PAIRS --
|
||||
|
||||
style.wh_gray = cpair(colors.white, colors.gray)
|
||||
|
||||
style.bw_fg_bg = cpair(colors.black, colors.white)
|
||||
style.text_colors = cpair(colors.black, colors.lightGray)
|
||||
style.lu_colors = cpair(colors.gray, colors.gray)
|
||||
style.hzd_fg_bg = style.wh_gray
|
||||
style.dis_colors = cpair(colors.white, colors.lightGray)
|
||||
|
||||
style.ind_grn = cpair(colors.green, colors.gray)
|
||||
style.ind_yel = cpair(colors.yellow, colors.gray)
|
||||
style.ind_red = cpair(colors.red, colors.gray)
|
||||
style.ind_wht = style.wh_gray
|
||||
|
||||
-- UI COMPONENTS --
|
||||
|
||||
style.reactor = {
|
||||
-- reactor states
|
||||
@@ -151,8 +201,121 @@ style.imatrix = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "HIGH CHARGE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.sps = {
|
||||
-- SPS states
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.dtank = {
|
||||
-- dynamic tank states
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ONLINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "LOW FILL"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "FILLED"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
style.waste = {
|
||||
-- auto waste processing states
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "PLUTONIUM"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.cyan),
|
||||
text = "POLONIUM"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.purple),
|
||||
text = "ANTI MATTER"
|
||||
}
|
||||
},
|
||||
states_abbrv = {
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "Pu"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.cyan),
|
||||
text = "Po"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.purple),
|
||||
text = "AM"
|
||||
}
|
||||
},
|
||||
-- process radio button options
|
||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||
-- unit waste selection
|
||||
unit_opts = {
|
||||
{
|
||||
text = "Auto",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.white, colors.gray)
|
||||
},
|
||||
{
|
||||
text = "Pu",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.green)
|
||||
},
|
||||
{
|
||||
text = "Po",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.cyan)
|
||||
},
|
||||
{
|
||||
text = "AM",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.purple)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
|
||||
@@ -7,6 +7,8 @@ local flasher = require("graphics.flasher")
|
||||
|
||||
local core = {}
|
||||
|
||||
core.version = "1.1.1"
|
||||
|
||||
core.flasher = flasher
|
||||
core.events = events
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ local element = {}
|
||||
|
||||
---@alias graphics_args graphics_args_generic
|
||||
---|waiting_args
|
||||
---|app_button_args
|
||||
---|checkbox_args
|
||||
---|hazard_button_args
|
||||
---|multi_button_args
|
||||
---|push_button_args
|
||||
@@ -70,7 +72,8 @@ function element.new(args, child_offset_x, child_offset_y)
|
||||
p_window = nil, ---@type table
|
||||
position = { x = 1, y = 1 }, ---@type coordinate_2d
|
||||
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
|
||||
next_y = 1,
|
||||
next_y = 1, -- next child y coordinate
|
||||
next_id = 0, -- next child ID
|
||||
subscriptions = {},
|
||||
mt = {}
|
||||
}
|
||||
@@ -351,13 +354,14 @@ function element.new(args, child_offset_x, child_offset_y)
|
||||
|
||||
local child_element = child.get()
|
||||
|
||||
if key == nil then
|
||||
table.insert(protected.children, child_element)
|
||||
return #protected.children
|
||||
else
|
||||
protected.children[key] = child_element
|
||||
return key
|
||||
local id = key ---@type string|integer|nil
|
||||
if id == nil then
|
||||
id = self.next_id
|
||||
self.next_id = self.next_id + 1
|
||||
end
|
||||
|
||||
protected.children[id] = child_element
|
||||
return id
|
||||
end
|
||||
|
||||
-- remove a child element
|
||||
@@ -512,19 +516,21 @@ function element.new(args, child_offset_x, child_offset_y)
|
||||
|
||||
-- FUNCTION CALLBACKS --
|
||||
|
||||
-- handle a monitor touch or mouse click
|
||||
-- handle a monitor touch or mouse click if this element is visible
|
||||
---@param event mouse_interaction mouse interaction event
|
||||
function public.handle_mouse(event)
|
||||
local x_ini, y_ini = event.initial.x, event.initial.y
|
||||
if protected.window.isVisible() then
|
||||
local x_ini, y_ini = event.initial.x, event.initial.y
|
||||
|
||||
local ini_in = protected.in_window_bounds(x_ini, y_ini)
|
||||
local ini_in = protected.in_window_bounds(x_ini, y_ini)
|
||||
|
||||
if ini_in then
|
||||
local event_T = core.events.mouse_transposed(event, self.position.x, self.position.y)
|
||||
if ini_in then
|
||||
local event_T = core.events.mouse_transposed(event, self.position.x, self.position.y)
|
||||
|
||||
-- handle the mouse event then pass to children
|
||||
protected.handle_mouse(event_T)
|
||||
for _, child in pairs(protected.children) do child.handle_mouse(event_T) end
|
||||
-- handle the mouse event then pass to children
|
||||
protected.handle_mouse(event_T)
|
||||
for _, child in pairs(protected.children) do child.handle_mouse(event_T) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new color map
|
||||
|
||||
130
graphics/elements/controls/app.lua
Normal file
130
graphics/elements/controls/app.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
-- App Button Graphics Element
|
||||
|
||||
local tcd = require("scada-common.tcd")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local element = require("graphics.element")
|
||||
|
||||
local CLICK_TYPE = core.events.CLICK_TYPE
|
||||
|
||||
---@class app_button_args
|
||||
---@field text string app icon text
|
||||
---@field title string app title text
|
||||
---@field callback function function to call on touch
|
||||
---@field app_fg_bg cpair app icon foreground/background colors
|
||||
---@field active_fg_bg? cpair foreground/background colors when pressed
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new app button
|
||||
---@param args app_button_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function app_button(args)
|
||||
assert(type(args.text) == "string", "graphics.elements.controls.app: text is a required field")
|
||||
assert(type(args.title) == "string", "graphics.elements.controls.app: title is a required field")
|
||||
assert(type(args.callback) == "function", "graphics.elements.controls.app: callback is a required field")
|
||||
assert(type(args.app_fg_bg) == "table", "graphics.elements.controls.app: app_fg_bg is a required field")
|
||||
|
||||
args.height = 4
|
||||
args.width = 5
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
-- write app title, centered
|
||||
e.window.setCursorPos(1, 4)
|
||||
e.window.setCursorPos(math.floor((e.frame.w - string.len(args.title)) / 2) + 1, 4)
|
||||
e.window.write(args.title)
|
||||
|
||||
-- draw the app button
|
||||
local function draw()
|
||||
local fgd = args.app_fg_bg.fgd
|
||||
local bkg = args.app_fg_bg.bkg
|
||||
|
||||
if e.value then
|
||||
fgd = args.active_fg_bg.fgd
|
||||
bkg = args.active_fg_bg.bkg
|
||||
end
|
||||
|
||||
-- draw icon
|
||||
e.window.setCursorPos(1, 1)
|
||||
e.window.setTextColor(fgd)
|
||||
e.window.setBackgroundColor(bkg)
|
||||
e.window.write("\x9f\x83\x83\x83")
|
||||
e.window.setTextColor(bkg)
|
||||
e.window.setBackgroundColor(fgd)
|
||||
e.window.write("\x90")
|
||||
e.window.setTextColor(fgd)
|
||||
e.window.setBackgroundColor(bkg)
|
||||
e.window.setCursorPos(1, 2)
|
||||
e.window.write("\x95 ")
|
||||
e.window.setTextColor(bkg)
|
||||
e.window.setBackgroundColor(fgd)
|
||||
e.window.write("\x95")
|
||||
e.window.setCursorPos(1, 3)
|
||||
e.window.write("\x82\x8f\x8f\x8f\x81")
|
||||
|
||||
-- write the icon text
|
||||
e.window.setCursorPos(3, 2)
|
||||
e.window.setTextColor(fgd)
|
||||
e.window.setBackgroundColor(bkg)
|
||||
e.window.write(args.text)
|
||||
end
|
||||
|
||||
-- draw the app 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 app 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
|
||||
function e.handle_mouse(event)
|
||||
if e.enabled then
|
||||
if event.type == CLICK_TYPE.TAP then
|
||||
show_pressed()
|
||||
-- show as unpressed in 0.25 seconds
|
||||
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()
|
||||
if e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
args.callback()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- set the value (true simulates pressing the app 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.UP, 1, 1)) end
|
||||
end
|
||||
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return app_button
|
||||
85
graphics/elements/controls/checkbox.lua
Normal file
85
graphics/elements/controls/checkbox.lua
Normal file
@@ -0,0 +1,85 @@
|
||||
-- Checkbox Graphics Element
|
||||
|
||||
local core = require("graphics.core")
|
||||
local element = require("graphics.element")
|
||||
|
||||
---@class checkbox_args
|
||||
---@field label string checkbox text
|
||||
---@field box_fg_bg cpair colors for checkbox
|
||||
---@field callback function function to call on press
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new checkbox control
|
||||
---@param args checkbox_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function checkbox(args)
|
||||
assert(type(args.label) == "string", "graphics.elements.controls.checkbox: label is a required field")
|
||||
assert(type(args.box_fg_bg) == "table", "graphics.elements.controls.checkbox: box_fg_bg is a required field")
|
||||
assert(type(args.callback) == "function", "graphics.elements.controls.checkbox: callback is a required field")
|
||||
|
||||
args.height = 1
|
||||
args.width = 3 + string.len(args.label)
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
e.value = false
|
||||
|
||||
-- show the button state
|
||||
local function draw()
|
||||
e.window.setCursorPos(1, 1)
|
||||
|
||||
if e.value then
|
||||
-- show as selected
|
||||
e.window.setTextColor(args.box_fg_bg.bkg)
|
||||
e.window.setBackgroundColor(args.box_fg_bg.fgd)
|
||||
e.window.write("\x88")
|
||||
e.window.setTextColor(args.box_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
e.window.write("\x95")
|
||||
else
|
||||
-- show as unselected
|
||||
e.window.setTextColor(e.fg_bg.bkg)
|
||||
e.window.setBackgroundColor(args.box_fg_bg.bkg)
|
||||
e.window.write("\x88")
|
||||
e.window.setTextColor(args.box_fg_bg.bkg)
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
e.window.write("\x95")
|
||||
end
|
||||
end
|
||||
|
||||
-- handle mouse interaction
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
if e.enabled and core.events.was_clicked(event.type) then
|
||||
e.value = not e.value
|
||||
draw()
|
||||
args.callback(e.value)
|
||||
end
|
||||
end
|
||||
|
||||
-- set the value
|
||||
---@param val integer new value
|
||||
function e.set_value(val)
|
||||
e.value = val
|
||||
draw()
|
||||
end
|
||||
|
||||
-- write label text
|
||||
e.window.setCursorPos(3, 1)
|
||||
e.window.setTextColor(e.fg_bg.fgd)
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
e.window.write(args.label)
|
||||
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return checkbox
|
||||
@@ -14,7 +14,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -16,7 +16,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -13,7 +13,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -16,7 +16,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -18,7 +18,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -6,7 +6,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
|
||||
-- new core map box
|
||||
---@nodiscard
|
||||
|
||||
@@ -14,7 +14,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width integer length
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -10,7 +10,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -16,7 +16,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width integer length
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -14,7 +14,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width integer length
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -15,7 +15,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field height? integer 1 if omitted, must be an odd number
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
@@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -15,7 +15,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -7,7 +7,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -11,9 +11,15 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
---@class _pipe_map_entry
|
||||
---@field atr boolean align top right (or bottom left for false)
|
||||
---@field thin boolean thin pipe or not
|
||||
---@field fg string foreground blit
|
||||
---@field bg string background blit
|
||||
|
||||
-- new pipe network
|
||||
---@param args pipenet_args
|
||||
---@return graphics_element element, element_id id
|
||||
@@ -44,102 +50,264 @@ local function pipenet(args)
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
-- draw all pipes
|
||||
-- determine if there are any thin pipes involved
|
||||
local any_thin = false
|
||||
for p = 1, #args.pipes do
|
||||
local pipe = args.pipes[p] ---@type pipe
|
||||
any_thin = args.pipes[p].thin
|
||||
if any_thin then break end
|
||||
end
|
||||
|
||||
local x = 1 + pipe.x1
|
||||
local y = 1 + pipe.y1
|
||||
if not any_thin then
|
||||
-- draw all pipes
|
||||
for p = 1, #args.pipes do
|
||||
local pipe = args.pipes[p] ---@type pipe
|
||||
|
||||
local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1)
|
||||
local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1)
|
||||
local x = 1 + pipe.x1
|
||||
local y = 1 + pipe.y1
|
||||
|
||||
e.window.setCursorPos(x, y)
|
||||
local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1)
|
||||
local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1)
|
||||
|
||||
local c = core.cpair(pipe.color, e.fg_bg.bkg)
|
||||
if pipe.thin then
|
||||
x_step = util.trinary(pipe.x1 == pipe.x2, 0, x_step)
|
||||
y_step = util.trinary(pipe.y1 == pipe.y2, 0, y_step)
|
||||
end
|
||||
|
||||
if pipe.align_tr then
|
||||
-- cross width then height
|
||||
for i = 1, pipe.w do
|
||||
if pipe.thin then
|
||||
if i == pipe.w then
|
||||
-- corner
|
||||
if y_step > 0 then
|
||||
e.window.blit("\x93", c.blit_bkg, c.blit_fgd)
|
||||
e.window.setCursorPos(x, y)
|
||||
|
||||
local c = core.cpair(pipe.color, e.fg_bg.bkg)
|
||||
|
||||
if pipe.align_tr then
|
||||
-- cross width then height
|
||||
for i = 1, pipe.w do
|
||||
if pipe.thin then
|
||||
if i == pipe.w then
|
||||
-- corner
|
||||
if y_step > 0 then
|
||||
e.window.blit("\x93", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit("\x8e", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
else
|
||||
e.window.blit("\x8e", c.blit_fgd, c.blit_bkg)
|
||||
e.window.blit("\x8c", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
else
|
||||
if i == pipe.w and y_step > 0 then
|
||||
-- corner
|
||||
e.window.blit(" ", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit("\x8f", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
end
|
||||
|
||||
x = x + x_step
|
||||
e.window.setCursorPos(x, y)
|
||||
end
|
||||
|
||||
-- back up one
|
||||
x = x - x_step
|
||||
|
||||
for _ = 1, pipe.h - 1 do
|
||||
y = y + y_step
|
||||
e.window.setCursorPos(x, y)
|
||||
|
||||
if pipe.thin then
|
||||
e.window.blit("\x95", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit(" ", c.blit_bkg, c.blit_fgd)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- cross height then width
|
||||
for i = 1, pipe.h do
|
||||
if pipe.thin then
|
||||
if i == pipe.h then
|
||||
-- corner
|
||||
if y_step < 0 then
|
||||
e.window.blit("\x97", c.blit_bkg, c.blit_fgd)
|
||||
elseif y_step > 0 then
|
||||
e.window.blit("\x8d", c.blit_fgd, c.blit_bkg)
|
||||
else
|
||||
e.window.blit("\x8c", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
else
|
||||
e.window.blit("\x95", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
else
|
||||
if i == pipe.h and y_step < 0 then
|
||||
-- corner
|
||||
e.window.blit("\x83", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit(" ", c.blit_bkg, c.blit_fgd)
|
||||
end
|
||||
end
|
||||
|
||||
y = y + y_step
|
||||
e.window.setCursorPos(x, y)
|
||||
end
|
||||
|
||||
-- back up one
|
||||
y = y - y_step
|
||||
|
||||
for _ = 1, pipe.w - 1 do
|
||||
x = x + x_step
|
||||
e.window.setCursorPos(x, y)
|
||||
|
||||
if pipe.thin then
|
||||
e.window.blit("\x8c", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
else
|
||||
if i == pipe.w and y_step > 0 then
|
||||
-- corner
|
||||
e.window.blit(" ", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit("\x8f", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
end
|
||||
|
||||
x = x + x_step
|
||||
e.window.setCursorPos(x, y)
|
||||
end
|
||||
|
||||
-- back up one
|
||||
x = x - x_step
|
||||
|
||||
for _ = 1, pipe.h - 1 do
|
||||
y = y + y_step
|
||||
e.window.setCursorPos(x, y)
|
||||
|
||||
if pipe.thin then
|
||||
e.window.blit("\x95", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit(" ", c.blit_bkg, c.blit_fgd)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- cross height then width
|
||||
for i = 1, pipe.h do
|
||||
if pipe.thin then
|
||||
if i == pipe.h then
|
||||
-- corner
|
||||
if y_step < 0 then
|
||||
e.window.blit("\x97", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit("\x8d", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
else
|
||||
e.window.blit("\x95", c.blit_fgd, c.blit_bkg)
|
||||
end
|
||||
else
|
||||
if i == pipe.h and y_step < 0 then
|
||||
-- corner
|
||||
e.window.blit("\x83", c.blit_bkg, c.blit_fgd)
|
||||
else
|
||||
e.window.blit(" ", c.blit_bkg, c.blit_fgd)
|
||||
end
|
||||
end
|
||||
|
||||
y = y + y_step
|
||||
e.window.setCursorPos(x, y)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- build map if using thin pipes, easist way to check adjacent blocks (cannot 'cheat' like with standard width)
|
||||
local map = {}
|
||||
|
||||
-- back up one
|
||||
y = y - y_step
|
||||
-- allocate map
|
||||
for x = 1, args.width do
|
||||
table.insert(map, {})
|
||||
for _ = 1, args.height do table.insert(map[x], false) end
|
||||
end
|
||||
|
||||
for _ = 1, pipe.w - 1 do
|
||||
x = x + x_step
|
||||
e.window.setCursorPos(x, y)
|
||||
-- build map
|
||||
for p = 1, #args.pipes do
|
||||
local pipe = args.pipes[p] ---@type pipe
|
||||
|
||||
if pipe.thin then
|
||||
e.window.blit("\x8c", c.blit_fgd, c.blit_bkg)
|
||||
else
|
||||
e.window.blit("\x83", c.blit_bkg, c.blit_fgd)
|
||||
local x = 1 + pipe.x1
|
||||
local y = 1 + pipe.y1
|
||||
|
||||
local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1)
|
||||
local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1)
|
||||
|
||||
local entry = { atr = pipe.align_tr, thin = pipe.thin, fg = colors.toBlit(pipe.color), bg = e.fg_bg.blit_bkg }
|
||||
|
||||
if pipe.align_tr then
|
||||
-- cross width then height
|
||||
for _ = 1, pipe.w do
|
||||
map[x][y] = entry
|
||||
x = x + x_step
|
||||
end
|
||||
|
||||
x = x - x_step -- back up one
|
||||
|
||||
for _ = 1, pipe.h do
|
||||
map[x][y] = entry
|
||||
y = y + y_step
|
||||
end
|
||||
else
|
||||
-- cross height then width
|
||||
for _ = 1, pipe.h do
|
||||
map[x][y] = entry
|
||||
y = y + y_step
|
||||
end
|
||||
|
||||
y = y - y_step -- back up one
|
||||
|
||||
for _ = 1, pipe.w do
|
||||
map[x][y] = entry
|
||||
x = x + x_step
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- render
|
||||
for x = 1, args.width do
|
||||
for y = 1, args.height do
|
||||
local entry = map[x][y] ---@type _pipe_map_entry|false
|
||||
local char
|
||||
local invert = false
|
||||
|
||||
if entry ~= false then
|
||||
local function check(cx, cy)
|
||||
return (map[cx] ~= nil) and (map[cx][cy] ~= nil) and (map[cx][cy] ~= false) and (map[cx][cy].fg == entry.fg)
|
||||
end
|
||||
|
||||
if entry.thin then
|
||||
if check(x - 1, y) then -- if left
|
||||
if check(x, y - 1) then -- if above
|
||||
if check(x + 1, y) then -- if right
|
||||
if check(x, y + 1) then -- if below
|
||||
char = util.trinary(entry.atr, "\x91", "\x9d")
|
||||
invert = entry.atr
|
||||
else -- not below
|
||||
char = util.trinary(entry.atr, "\x8e", "\x8d")
|
||||
end
|
||||
else -- not right
|
||||
if check(x, y + 1) then -- if below
|
||||
char = util.trinary(entry.atr, "\x91", "\x95")
|
||||
invert = entry.atr
|
||||
else -- not below
|
||||
char = util.trinary(entry.atr, "\x8e", "\x85")
|
||||
end
|
||||
end
|
||||
elseif check(x, y + 1) then-- not above, if below
|
||||
if check(x + 1, y) then -- if right
|
||||
char = util.trinary(entry.atr, "\x93", "\x9c")
|
||||
invert = entry.atr
|
||||
else -- not right
|
||||
char = util.trinary(entry.atr, "\x93", "\x94")
|
||||
invert = entry.atr
|
||||
end
|
||||
else -- not above, not below
|
||||
char = "\x8c"
|
||||
end
|
||||
elseif check(x + 1, y) then -- not left, if right
|
||||
if check(x, y - 1) then -- if above
|
||||
if check(x, y + 1) then -- if below
|
||||
char = util.trinary(entry.atr, "\x95", "\x9d")
|
||||
invert = entry.atr
|
||||
else -- not below
|
||||
char = util.trinary(entry.atr, "\x8a", "\x8d")
|
||||
end
|
||||
else -- not above
|
||||
if check(x, y + 1) then -- if below
|
||||
char = util.trinary(entry.atr, "\x97", "\x9c")
|
||||
invert = entry.atr
|
||||
else -- not below
|
||||
char = "\x8c"
|
||||
end
|
||||
end
|
||||
else -- not left, not right
|
||||
char = "\x95"
|
||||
invert = entry.atr
|
||||
end
|
||||
else
|
||||
if check(x, y - 1) then -- above
|
||||
-- not below and (if left or right)
|
||||
if (not check(x, y + 1)) and (check(x - 1, y) or check(x + 1, y)) then
|
||||
char = util.trinary(entry.atr, "\x8f", " ")
|
||||
invert = not entry.atr
|
||||
else -- not below w/ sides only
|
||||
char = " "
|
||||
invert = true
|
||||
end
|
||||
elseif check(x, y + 1) then -- not above, if below
|
||||
-- if left or right
|
||||
if (check(x - 1, y) or check(x + 1, y)) then
|
||||
char = "\x83"
|
||||
invert = true
|
||||
else -- not left or right
|
||||
char = " "
|
||||
invert = true
|
||||
end
|
||||
else -- not above, not below
|
||||
char = util.trinary(entry.atr, "\x8f", "\x83")
|
||||
invert = not entry.atr
|
||||
end
|
||||
end
|
||||
|
||||
e.window.setCursorPos(x, y)
|
||||
|
||||
if invert then
|
||||
e.window.blit(char, entry.bg, entry.fg)
|
||||
else
|
||||
e.window.blit(char, entry.fg, entry.bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return e.complete()
|
||||
|
||||
@@ -11,7 +11,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -13,7 +13,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -11,7 +11,7 @@ local element = require("graphics.element")
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field y? integer auto incremented if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
|
||||
@@ -43,8 +43,10 @@ end
|
||||
|
||||
-- start/resume the flasher periodic
|
||||
function flasher.run()
|
||||
active = true
|
||||
callback_250ms()
|
||||
if not active then
|
||||
active = true
|
||||
callback_250ms()
|
||||
end
|
||||
end
|
||||
|
||||
-- clear all blinking indicators and stop the flasher periodic
|
||||
|
||||
19
imgen.py
19
imgen.py
@@ -23,11 +23,11 @@ def dir_size(path):
|
||||
return total
|
||||
|
||||
# get the version of an application at the provided path
|
||||
def get_version(path, is_comms = False):
|
||||
def get_version(path, is_lib = False):
|
||||
ver = ""
|
||||
string = "comms.version = \""
|
||||
string = ".version = \""
|
||||
|
||||
if not is_comms:
|
||||
if not is_lib:
|
||||
string = "_VERSION = \""
|
||||
|
||||
f = open(path, "r")
|
||||
@@ -48,7 +48,10 @@ def make_manifest(size):
|
||||
"versions" : {
|
||||
"installer" : get_version("./ccmsi.lua"),
|
||||
"bootloader" : get_version("./startup.lua"),
|
||||
"common" : get_version("./scada-common/util.lua", True),
|
||||
"comms" : get_version("./scada-common/comms.lua", True),
|
||||
"graphics" : get_version("./graphics/core.lua", True),
|
||||
"lockbox" : get_version("./lockbox/init.lua", True),
|
||||
"reactor-plc" : get_version("./reactor-plc/startup.lua"),
|
||||
"rtu" : get_version("./rtu/startup.lua"),
|
||||
"supervisor" : get_version("./supervisor/startup.lua"),
|
||||
@@ -69,11 +72,11 @@ def make_manifest(size):
|
||||
"pocket" : list_files("./pocket"),
|
||||
},
|
||||
"depends" : {
|
||||
"reactor-plc" : [ "system", "common", "graphics" ],
|
||||
"rtu" : [ "system", "common", "graphics" ],
|
||||
"supervisor" : [ "system", "common", "graphics" ],
|
||||
"coordinator" : [ "system", "common", "graphics" ],
|
||||
"pocket" : [ "system", "common", "graphics" ]
|
||||
"reactor-plc" : [ "system", "common", "graphics", "lockbox" ],
|
||||
"rtu" : [ "system", "common", "graphics", "lockbox" ],
|
||||
"supervisor" : [ "system", "common", "graphics", "lockbox" ],
|
||||
"coordinator" : [ "system", "common", "graphics", "lockbox" ],
|
||||
"pocket" : [ "system", "common", "graphics", "lockbox" ]
|
||||
},
|
||||
"sizes" : {
|
||||
# manifest file estimate
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,415 +0,0 @@
|
||||
local Array = require("lockbox.util.array");
|
||||
local Bit = require("lockbox.util.bit");
|
||||
|
||||
local XOR = Bit.bxor;
|
||||
|
||||
local SBOX = {
|
||||
[0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
|
||||
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
|
||||
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
|
||||
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
|
||||
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
|
||||
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
|
||||
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
|
||||
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
|
||||
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
|
||||
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
|
||||
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
|
||||
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
|
||||
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
|
||||
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
|
||||
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16};
|
||||
|
||||
local ISBOX = {
|
||||
[0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
|
||||
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
|
||||
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
|
||||
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
|
||||
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
|
||||
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
|
||||
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
|
||||
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
|
||||
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
|
||||
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
|
||||
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
|
||||
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
|
||||
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
|
||||
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
|
||||
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D};
|
||||
|
||||
local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, };
|
||||
local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, };
|
||||
|
||||
local ETABLE = {
|
||||
[0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35,
|
||||
0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA,
|
||||
0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31,
|
||||
0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD,
|
||||
0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88,
|
||||
0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A,
|
||||
0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3,
|
||||
0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0,
|
||||
0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41,
|
||||
0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75,
|
||||
0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80,
|
||||
0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54,
|
||||
0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA,
|
||||
0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E,
|
||||
0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17,
|
||||
0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01};
|
||||
|
||||
local LTABLE = {
|
||||
[0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03,
|
||||
0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1,
|
||||
0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78,
|
||||
0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E,
|
||||
0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38,
|
||||
0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10,
|
||||
0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA,
|
||||
0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57,
|
||||
0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8,
|
||||
0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0,
|
||||
0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7,
|
||||
0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D,
|
||||
0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1,
|
||||
0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB,
|
||||
0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5,
|
||||
0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07};
|
||||
|
||||
local MIXTABLE = {
|
||||
0x02, 0x03, 0x01, 0x01,
|
||||
0x01, 0x02, 0x03, 0x01,
|
||||
0x01, 0x01, 0x02, 0x03,
|
||||
0x03, 0x01, 0x01, 0x02};
|
||||
|
||||
local IMIXTABLE = {
|
||||
0x0E, 0x0B, 0x0D, 0x09,
|
||||
0x09, 0x0E, 0x0B, 0x0D,
|
||||
0x0D, 0x09, 0x0E, 0x0B,
|
||||
0x0B, 0x0D, 0x09, 0x0E};
|
||||
|
||||
local RCON = {
|
||||
[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
||||
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
||||
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
||||
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
||||
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
||||
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
||||
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
||||
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
||||
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
||||
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
||||
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
||||
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
||||
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
||||
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d};
|
||||
|
||||
|
||||
local GMUL = function(A, B)
|
||||
if(A == 0x01) then return B; end
|
||||
if(B == 0x01) then return A; end
|
||||
if(A == 0x00) then return 0; end
|
||||
if(B == 0x00) then return 0; end
|
||||
|
||||
local LA = LTABLE[A];
|
||||
local LB = LTABLE[B];
|
||||
|
||||
local sum = LA + LB;
|
||||
if (sum > 0xFF) then sum = sum - 0xFF; end
|
||||
|
||||
return ETABLE[sum];
|
||||
end
|
||||
|
||||
local byteSub = Array.substitute;
|
||||
|
||||
local shiftRow = Array.permute;
|
||||
|
||||
local mixCol = function(i, mix)
|
||||
local out = {};
|
||||
|
||||
local a, b, c, d;
|
||||
|
||||
a = GMUL(i[ 1], mix[ 1]);
|
||||
b = GMUL(i[ 2], mix[ 2]);
|
||||
c = GMUL(i[ 3], mix[ 3]);
|
||||
d = GMUL(i[ 4], mix[ 4]);
|
||||
out[ 1] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[ 5]);
|
||||
b = GMUL(i[ 2], mix[ 6]);
|
||||
c = GMUL(i[ 3], mix[ 7]);
|
||||
d = GMUL(i[ 4], mix[ 8]);
|
||||
out[ 2] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[ 9]);
|
||||
b = GMUL(i[ 2], mix[10]);
|
||||
c = GMUL(i[ 3], mix[11]);
|
||||
d = GMUL(i[ 4], mix[12]);
|
||||
out[ 3] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[13]);
|
||||
b = GMUL(i[ 2], mix[14]);
|
||||
c = GMUL(i[ 3], mix[15]);
|
||||
d = GMUL(i[ 4], mix[16]);
|
||||
out[ 4] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[ 5], mix[ 1]);
|
||||
b = GMUL(i[ 6], mix[ 2]);
|
||||
c = GMUL(i[ 7], mix[ 3]);
|
||||
d = GMUL(i[ 8], mix[ 4]);
|
||||
out[ 5] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[ 5]);
|
||||
b = GMUL(i[ 6], mix[ 6]);
|
||||
c = GMUL(i[ 7], mix[ 7]);
|
||||
d = GMUL(i[ 8], mix[ 8]);
|
||||
out[ 6] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[ 9]);
|
||||
b = GMUL(i[ 6], mix[10]);
|
||||
c = GMUL(i[ 7], mix[11]);
|
||||
d = GMUL(i[ 8], mix[12]);
|
||||
out[ 7] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[13]);
|
||||
b = GMUL(i[ 6], mix[14]);
|
||||
c = GMUL(i[ 7], mix[15]);
|
||||
d = GMUL(i[ 8], mix[16]);
|
||||
out[ 8] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[ 9], mix[ 1]);
|
||||
b = GMUL(i[10], mix[ 2]);
|
||||
c = GMUL(i[11], mix[ 3]);
|
||||
d = GMUL(i[12], mix[ 4]);
|
||||
out[ 9] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[ 5]);
|
||||
b = GMUL(i[10], mix[ 6]);
|
||||
c = GMUL(i[11], mix[ 7]);
|
||||
d = GMUL(i[12], mix[ 8]);
|
||||
out[10] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[ 9]);
|
||||
b = GMUL(i[10], mix[10]);
|
||||
c = GMUL(i[11], mix[11]);
|
||||
d = GMUL(i[12], mix[12]);
|
||||
out[11] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[13]);
|
||||
b = GMUL(i[10], mix[14]);
|
||||
c = GMUL(i[11], mix[15]);
|
||||
d = GMUL(i[12], mix[16]);
|
||||
out[12] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[13], mix[ 1]);
|
||||
b = GMUL(i[14], mix[ 2]);
|
||||
c = GMUL(i[15], mix[ 3]);
|
||||
d = GMUL(i[16], mix[ 4]);
|
||||
out[13] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[ 5]);
|
||||
b = GMUL(i[14], mix[ 6]);
|
||||
c = GMUL(i[15], mix[ 7]);
|
||||
d = GMUL(i[16], mix[ 8]);
|
||||
out[14] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[ 9]);
|
||||
b = GMUL(i[14], mix[10]);
|
||||
c = GMUL(i[15], mix[11]);
|
||||
d = GMUL(i[16], mix[12]);
|
||||
out[15] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[13]);
|
||||
b = GMUL(i[14], mix[14]);
|
||||
c = GMUL(i[15], mix[15]);
|
||||
d = GMUL(i[16], mix[16]);
|
||||
out[16] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
return out;
|
||||
end
|
||||
|
||||
local keyRound = function(key, round)
|
||||
local out = {};
|
||||
|
||||
out[ 1] = XOR(key[ 1], XOR(SBOX[key[14]], RCON[round]));
|
||||
out[ 2] = XOR(key[ 2], SBOX[key[15]]);
|
||||
out[ 3] = XOR(key[ 3], SBOX[key[16]]);
|
||||
out[ 4] = XOR(key[ 4], SBOX[key[13]]);
|
||||
|
||||
out[ 5] = XOR(out[ 1], key[ 5]);
|
||||
out[ 6] = XOR(out[ 2], key[ 6]);
|
||||
out[ 7] = XOR(out[ 3], key[ 7]);
|
||||
out[ 8] = XOR(out[ 4], key[ 8]);
|
||||
|
||||
out[ 9] = XOR(out[ 5], key[ 9]);
|
||||
out[10] = XOR(out[ 6], key[10]);
|
||||
out[11] = XOR(out[ 7], key[11]);
|
||||
out[12] = XOR(out[ 8], key[12]);
|
||||
|
||||
out[13] = XOR(out[ 9], key[13]);
|
||||
out[14] = XOR(out[10], key[14]);
|
||||
out[15] = XOR(out[11], key[15]);
|
||||
out[16] = XOR(out[12], key[16]);
|
||||
|
||||
return out;
|
||||
end
|
||||
|
||||
local keyExpand = function(key)
|
||||
local keys = {};
|
||||
|
||||
local temp = key;
|
||||
|
||||
keys[1] = temp;
|
||||
|
||||
for i = 1, 10 do
|
||||
temp = keyRound(temp, i);
|
||||
keys[i + 1] = temp;
|
||||
end
|
||||
|
||||
return keys;
|
||||
|
||||
end
|
||||
|
||||
local addKey = Array.XOR;
|
||||
|
||||
|
||||
|
||||
local AES = {};
|
||||
|
||||
AES.blockSize = 16;
|
||||
|
||||
AES.encrypt = function(_key, block)
|
||||
|
||||
local key = keyExpand(_key);
|
||||
|
||||
--round 0
|
||||
block = addKey(block, key[1]);
|
||||
|
||||
--round 1
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[2]);
|
||||
|
||||
--round 2
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[3]);
|
||||
|
||||
--round 3
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[4]);
|
||||
|
||||
--round 4
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[5]);
|
||||
|
||||
--round 5
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[6]);
|
||||
|
||||
--round 6
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[7]);
|
||||
|
||||
--round 7
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[8]);
|
||||
|
||||
--round 8
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[9]);
|
||||
|
||||
--round 9
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[10]);
|
||||
|
||||
--round 10
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = addKey(block, key[11]);
|
||||
|
||||
return block;
|
||||
|
||||
end
|
||||
|
||||
AES.decrypt = function(_key, block)
|
||||
|
||||
local key = keyExpand(_key);
|
||||
|
||||
--round 0
|
||||
block = addKey(block, key[11]);
|
||||
|
||||
--round 1
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[10]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 2
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[9]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 3
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[8]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 4
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[7]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 5
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[6]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 6
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[5]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 7
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[4]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 8
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[3]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 9
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[2]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 10
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[1]);
|
||||
|
||||
return block;
|
||||
end
|
||||
|
||||
return AES;
|
||||
@@ -1,462 +0,0 @@
|
||||
|
||||
local Array = require("lockbox.util.array");
|
||||
local Bit = require("lockbox.util.bit");
|
||||
|
||||
local XOR = Bit.bxor;
|
||||
|
||||
local SBOX = {
|
||||
[0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
|
||||
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
|
||||
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
|
||||
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
|
||||
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
|
||||
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
|
||||
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
|
||||
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
|
||||
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
|
||||
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
|
||||
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
|
||||
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
|
||||
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
|
||||
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
|
||||
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16};
|
||||
|
||||
local ISBOX = {
|
||||
[0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
|
||||
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
|
||||
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
|
||||
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
|
||||
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
|
||||
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
|
||||
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
|
||||
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
|
||||
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
|
||||
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
|
||||
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
|
||||
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
|
||||
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
|
||||
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
|
||||
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D};
|
||||
|
||||
local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, };
|
||||
local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, };
|
||||
|
||||
local ETABLE = {
|
||||
[0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35,
|
||||
0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA,
|
||||
0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31,
|
||||
0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD,
|
||||
0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88,
|
||||
0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A,
|
||||
0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3,
|
||||
0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0,
|
||||
0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41,
|
||||
0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75,
|
||||
0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80,
|
||||
0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54,
|
||||
0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA,
|
||||
0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E,
|
||||
0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17,
|
||||
0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01};
|
||||
|
||||
local LTABLE = {
|
||||
[0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03,
|
||||
0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1,
|
||||
0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78,
|
||||
0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E,
|
||||
0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38,
|
||||
0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10,
|
||||
0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA,
|
||||
0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57,
|
||||
0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8,
|
||||
0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0,
|
||||
0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7,
|
||||
0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D,
|
||||
0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1,
|
||||
0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB,
|
||||
0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5,
|
||||
0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07};
|
||||
|
||||
local MIXTABLE = {
|
||||
0x02, 0x03, 0x01, 0x01,
|
||||
0x01, 0x02, 0x03, 0x01,
|
||||
0x01, 0x01, 0x02, 0x03,
|
||||
0x03, 0x01, 0x01, 0x02};
|
||||
|
||||
local IMIXTABLE = {
|
||||
0x0E, 0x0B, 0x0D, 0x09,
|
||||
0x09, 0x0E, 0x0B, 0x0D,
|
||||
0x0D, 0x09, 0x0E, 0x0B,
|
||||
0x0B, 0x0D, 0x09, 0x0E};
|
||||
|
||||
local RCON = {
|
||||
[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
||||
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
||||
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
||||
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
||||
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
||||
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
||||
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
||||
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
||||
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
||||
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
||||
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
||||
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
||||
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
||||
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d};
|
||||
|
||||
|
||||
local GMUL = function(A, B)
|
||||
if(A == 0x01) then return B; end
|
||||
if(B == 0x01) then return A; end
|
||||
if(A == 0x00) then return 0; end
|
||||
if(B == 0x00) then return 0; end
|
||||
|
||||
local LA = LTABLE[A];
|
||||
local LB = LTABLE[B];
|
||||
|
||||
local sum = LA + LB;
|
||||
if (sum > 0xFF) then sum = sum - 0xFF; end
|
||||
|
||||
return ETABLE[sum];
|
||||
end
|
||||
|
||||
local byteSub = Array.substitute;
|
||||
|
||||
local shiftRow = Array.permute;
|
||||
|
||||
local mixCol = function(i, mix)
|
||||
local out = {};
|
||||
|
||||
local a, b, c, d;
|
||||
|
||||
a = GMUL(i[ 1], mix[ 1]);
|
||||
b = GMUL(i[ 2], mix[ 2]);
|
||||
c = GMUL(i[ 3], mix[ 3]);
|
||||
d = GMUL(i[ 4], mix[ 4]);
|
||||
out[ 1] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[ 5]);
|
||||
b = GMUL(i[ 2], mix[ 6]);
|
||||
c = GMUL(i[ 3], mix[ 7]);
|
||||
d = GMUL(i[ 4], mix[ 8]);
|
||||
out[ 2] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[ 9]);
|
||||
b = GMUL(i[ 2], mix[10]);
|
||||
c = GMUL(i[ 3], mix[11]);
|
||||
d = GMUL(i[ 4], mix[12]);
|
||||
out[ 3] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[13]);
|
||||
b = GMUL(i[ 2], mix[14]);
|
||||
c = GMUL(i[ 3], mix[15]);
|
||||
d = GMUL(i[ 4], mix[16]);
|
||||
out[ 4] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[ 5], mix[ 1]);
|
||||
b = GMUL(i[ 6], mix[ 2]);
|
||||
c = GMUL(i[ 7], mix[ 3]);
|
||||
d = GMUL(i[ 8], mix[ 4]);
|
||||
out[ 5] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[ 5]);
|
||||
b = GMUL(i[ 6], mix[ 6]);
|
||||
c = GMUL(i[ 7], mix[ 7]);
|
||||
d = GMUL(i[ 8], mix[ 8]);
|
||||
out[ 6] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[ 9]);
|
||||
b = GMUL(i[ 6], mix[10]);
|
||||
c = GMUL(i[ 7], mix[11]);
|
||||
d = GMUL(i[ 8], mix[12]);
|
||||
out[ 7] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[13]);
|
||||
b = GMUL(i[ 6], mix[14]);
|
||||
c = GMUL(i[ 7], mix[15]);
|
||||
d = GMUL(i[ 8], mix[16]);
|
||||
out[ 8] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[ 9], mix[ 1]);
|
||||
b = GMUL(i[10], mix[ 2]);
|
||||
c = GMUL(i[11], mix[ 3]);
|
||||
d = GMUL(i[12], mix[ 4]);
|
||||
out[ 9] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[ 5]);
|
||||
b = GMUL(i[10], mix[ 6]);
|
||||
c = GMUL(i[11], mix[ 7]);
|
||||
d = GMUL(i[12], mix[ 8]);
|
||||
out[10] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[ 9]);
|
||||
b = GMUL(i[10], mix[10]);
|
||||
c = GMUL(i[11], mix[11]);
|
||||
d = GMUL(i[12], mix[12]);
|
||||
out[11] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[13]);
|
||||
b = GMUL(i[10], mix[14]);
|
||||
c = GMUL(i[11], mix[15]);
|
||||
d = GMUL(i[12], mix[16]);
|
||||
out[12] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[13], mix[ 1]);
|
||||
b = GMUL(i[14], mix[ 2]);
|
||||
c = GMUL(i[15], mix[ 3]);
|
||||
d = GMUL(i[16], mix[ 4]);
|
||||
out[13] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[ 5]);
|
||||
b = GMUL(i[14], mix[ 6]);
|
||||
c = GMUL(i[15], mix[ 7]);
|
||||
d = GMUL(i[16], mix[ 8]);
|
||||
out[14] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[ 9]);
|
||||
b = GMUL(i[14], mix[10]);
|
||||
c = GMUL(i[15], mix[11]);
|
||||
d = GMUL(i[16], mix[12]);
|
||||
out[15] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[13]);
|
||||
b = GMUL(i[14], mix[14]);
|
||||
c = GMUL(i[15], mix[15]);
|
||||
d = GMUL(i[16], mix[16]);
|
||||
out[16] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
return out;
|
||||
end
|
||||
|
||||
local keyRound = function(key, round)
|
||||
local i = (round - 1) * 24;
|
||||
local out = key;
|
||||
|
||||
out[25 + i] = XOR(key[ 1 + i], XOR(SBOX[key[22 + i]], RCON[round]));
|
||||
out[26 + i] = XOR(key[ 2 + i], SBOX[key[23 + i]]);
|
||||
out[27 + i] = XOR(key[ 3 + i], SBOX[key[24 + i]]);
|
||||
out[28 + i] = XOR(key[ 4 + i], SBOX[key[21 + i]]);
|
||||
|
||||
out[29 + i] = XOR(out[25 + i], key[ 5 + i]);
|
||||
out[30 + i] = XOR(out[26 + i], key[ 6 + i]);
|
||||
out[31 + i] = XOR(out[27 + i], key[ 7 + i]);
|
||||
out[32 + i] = XOR(out[28 + i], key[ 8 + i]);
|
||||
|
||||
out[33 + i] = XOR(out[29 + i], key[ 9 + i]);
|
||||
out[34 + i] = XOR(out[30 + i], key[10 + i]);
|
||||
out[35 + i] = XOR(out[31 + i], key[11 + i]);
|
||||
out[36 + i] = XOR(out[32 + i], key[12 + i]);
|
||||
|
||||
out[37 + i] = XOR(out[33 + i], key[13 + i]);
|
||||
out[38 + i] = XOR(out[34 + i], key[14 + i]);
|
||||
out[39 + i] = XOR(out[35 + i], key[15 + i]);
|
||||
out[40 + i] = XOR(out[36 + i], key[16 + i]);
|
||||
|
||||
out[41 + i] = XOR(out[37 + i], key[17 + i]);
|
||||
out[42 + i] = XOR(out[38 + i], key[18 + i]);
|
||||
out[43 + i] = XOR(out[39 + i], key[19 + i]);
|
||||
out[44 + i] = XOR(out[40 + i], key[20 + i]);
|
||||
|
||||
out[45 + i] = XOR(out[41 + i], key[21 + i]);
|
||||
out[46 + i] = XOR(out[42 + i], key[22 + i]);
|
||||
out[47 + i] = XOR(out[43 + i], key[23 + i]);
|
||||
out[48 + i] = XOR(out[44 + i], key[24 + i]);
|
||||
|
||||
return out;
|
||||
end
|
||||
|
||||
local keyExpand = function(key)
|
||||
local bytes = Array.copy(key);
|
||||
|
||||
for i = 1, 8 do
|
||||
keyRound(bytes, i);
|
||||
end
|
||||
|
||||
local keys = {};
|
||||
|
||||
keys[ 1] = Array.slice(bytes, 1, 16);
|
||||
keys[ 2] = Array.slice(bytes, 17, 32);
|
||||
keys[ 3] = Array.slice(bytes, 33, 48);
|
||||
keys[ 4] = Array.slice(bytes, 49, 64);
|
||||
keys[ 5] = Array.slice(bytes, 65, 80);
|
||||
keys[ 6] = Array.slice(bytes, 81, 96);
|
||||
keys[ 7] = Array.slice(bytes, 97, 112);
|
||||
keys[ 8] = Array.slice(bytes, 113, 128);
|
||||
keys[ 9] = Array.slice(bytes, 129, 144);
|
||||
keys[10] = Array.slice(bytes, 145, 160);
|
||||
keys[11] = Array.slice(bytes, 161, 176);
|
||||
keys[12] = Array.slice(bytes, 177, 192);
|
||||
keys[13] = Array.slice(bytes, 193, 208);
|
||||
|
||||
return keys;
|
||||
|
||||
end
|
||||
|
||||
local addKey = Array.XOR;
|
||||
|
||||
|
||||
|
||||
local AES = {};
|
||||
|
||||
AES.blockSize = 16;
|
||||
|
||||
AES.encrypt = function(_key, block)
|
||||
|
||||
local key = keyExpand(_key);
|
||||
|
||||
--round 0
|
||||
block = addKey(block, key[1]);
|
||||
|
||||
--round 1
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[2]);
|
||||
|
||||
--round 2
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[3]);
|
||||
|
||||
--round 3
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[4]);
|
||||
|
||||
--round 4
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[5]);
|
||||
|
||||
--round 5
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[6]);
|
||||
|
||||
--round 6
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[7]);
|
||||
|
||||
--round 7
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[8]);
|
||||
|
||||
--round 8
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[9]);
|
||||
|
||||
--round 9
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[10]);
|
||||
|
||||
--round 10
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[11]);
|
||||
|
||||
--round 11
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[12]);
|
||||
|
||||
--round 12
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = addKey(block, key[13]);
|
||||
|
||||
return block;
|
||||
|
||||
end
|
||||
|
||||
AES.decrypt = function(_key, block)
|
||||
|
||||
local key = keyExpand(_key);
|
||||
|
||||
--round 0
|
||||
block = addKey(block, key[13]);
|
||||
|
||||
--round 1
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[12]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 2
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[11]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 3
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[10]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 4
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[9]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 5
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[8]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 6
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[7]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 7
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[6]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 8
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[5]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 9
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[4]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 10
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[3]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 11
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[2]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 12
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[1]);
|
||||
|
||||
return block;
|
||||
end
|
||||
|
||||
return AES;
|
||||
@@ -1,498 +0,0 @@
|
||||
local Array = require("lockbox.util.array");
|
||||
local Bit = require("lockbox.util.bit");
|
||||
|
||||
local XOR = Bit.bxor;
|
||||
|
||||
local SBOX = {
|
||||
[0] = 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
|
||||
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
|
||||
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
|
||||
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
|
||||
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
|
||||
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
|
||||
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
|
||||
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
|
||||
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
|
||||
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
|
||||
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
|
||||
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
|
||||
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
|
||||
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
|
||||
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16};
|
||||
|
||||
local ISBOX = {
|
||||
[0] = 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
|
||||
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
|
||||
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
|
||||
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
|
||||
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
|
||||
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
|
||||
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
|
||||
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
|
||||
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
|
||||
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
|
||||
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
|
||||
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
|
||||
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
|
||||
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
|
||||
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D};
|
||||
|
||||
local ROW_SHIFT = { 1, 6, 11, 16, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, };
|
||||
local IROW_SHIFT = { 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3, 16, 13, 10, 7, 4, };
|
||||
|
||||
local ETABLE = {
|
||||
[0] = 0x01, 0x03, 0x05, 0x0F, 0x11, 0x33, 0x55, 0xFF, 0x1A, 0x2E, 0x72, 0x96, 0xA1, 0xF8, 0x13, 0x35,
|
||||
0x5F, 0xE1, 0x38, 0x48, 0xD8, 0x73, 0x95, 0xA4, 0xF7, 0x02, 0x06, 0x0A, 0x1E, 0x22, 0x66, 0xAA,
|
||||
0xE5, 0x34, 0x5C, 0xE4, 0x37, 0x59, 0xEB, 0x26, 0x6A, 0xBE, 0xD9, 0x70, 0x90, 0xAB, 0xE6, 0x31,
|
||||
0x53, 0xF5, 0x04, 0x0C, 0x14, 0x3C, 0x44, 0xCC, 0x4F, 0xD1, 0x68, 0xB8, 0xD3, 0x6E, 0xB2, 0xCD,
|
||||
0x4C, 0xD4, 0x67, 0xA9, 0xE0, 0x3B, 0x4D, 0xD7, 0x62, 0xA6, 0xF1, 0x08, 0x18, 0x28, 0x78, 0x88,
|
||||
0x83, 0x9E, 0xB9, 0xD0, 0x6B, 0xBD, 0xDC, 0x7F, 0x81, 0x98, 0xB3, 0xCE, 0x49, 0xDB, 0x76, 0x9A,
|
||||
0xB5, 0xC4, 0x57, 0xF9, 0x10, 0x30, 0x50, 0xF0, 0x0B, 0x1D, 0x27, 0x69, 0xBB, 0xD6, 0x61, 0xA3,
|
||||
0xFE, 0x19, 0x2B, 0x7D, 0x87, 0x92, 0xAD, 0xEC, 0x2F, 0x71, 0x93, 0xAE, 0xE9, 0x20, 0x60, 0xA0,
|
||||
0xFB, 0x16, 0x3A, 0x4E, 0xD2, 0x6D, 0xB7, 0xC2, 0x5D, 0xE7, 0x32, 0x56, 0xFA, 0x15, 0x3F, 0x41,
|
||||
0xC3, 0x5E, 0xE2, 0x3D, 0x47, 0xC9, 0x40, 0xC0, 0x5B, 0xED, 0x2C, 0x74, 0x9C, 0xBF, 0xDA, 0x75,
|
||||
0x9F, 0xBA, 0xD5, 0x64, 0xAC, 0xEF, 0x2A, 0x7E, 0x82, 0x9D, 0xBC, 0xDF, 0x7A, 0x8E, 0x89, 0x80,
|
||||
0x9B, 0xB6, 0xC1, 0x58, 0xE8, 0x23, 0x65, 0xAF, 0xEA, 0x25, 0x6F, 0xB1, 0xC8, 0x43, 0xC5, 0x54,
|
||||
0xFC, 0x1F, 0x21, 0x63, 0xA5, 0xF4, 0x07, 0x09, 0x1B, 0x2D, 0x77, 0x99, 0xB0, 0xCB, 0x46, 0xCA,
|
||||
0x45, 0xCF, 0x4A, 0xDE, 0x79, 0x8B, 0x86, 0x91, 0xA8, 0xE3, 0x3E, 0x42, 0xC6, 0x51, 0xF3, 0x0E,
|
||||
0x12, 0x36, 0x5A, 0xEE, 0x29, 0x7B, 0x8D, 0x8C, 0x8F, 0x8A, 0x85, 0x94, 0xA7, 0xF2, 0x0D, 0x17,
|
||||
0x39, 0x4B, 0xDD, 0x7C, 0x84, 0x97, 0xA2, 0xFD, 0x1C, 0x24, 0x6C, 0xB4, 0xC7, 0x52, 0xF6, 0x01};
|
||||
|
||||
local LTABLE = {
|
||||
[0] = 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1A, 0xC6, 0x4B, 0xC7, 0x1B, 0x68, 0x33, 0xEE, 0xDF, 0x03,
|
||||
0x64, 0x04, 0xE0, 0x0E, 0x34, 0x8D, 0x81, 0xEF, 0x4C, 0x71, 0x08, 0xC8, 0xF8, 0x69, 0x1C, 0xC1,
|
||||
0x7D, 0xC2, 0x1D, 0xB5, 0xF9, 0xB9, 0x27, 0x6A, 0x4D, 0xE4, 0xA6, 0x72, 0x9A, 0xC9, 0x09, 0x78,
|
||||
0x65, 0x2F, 0x8A, 0x05, 0x21, 0x0F, 0xE1, 0x24, 0x12, 0xF0, 0x82, 0x45, 0x35, 0x93, 0xDA, 0x8E,
|
||||
0x96, 0x8F, 0xDB, 0xBD, 0x36, 0xD0, 0xCE, 0x94, 0x13, 0x5C, 0xD2, 0xF1, 0x40, 0x46, 0x83, 0x38,
|
||||
0x66, 0xDD, 0xFD, 0x30, 0xBF, 0x06, 0x8B, 0x62, 0xB3, 0x25, 0xE2, 0x98, 0x22, 0x88, 0x91, 0x10,
|
||||
0x7E, 0x6E, 0x48, 0xC3, 0xA3, 0xB6, 0x1E, 0x42, 0x3A, 0x6B, 0x28, 0x54, 0xFA, 0x85, 0x3D, 0xBA,
|
||||
0x2B, 0x79, 0x0A, 0x15, 0x9B, 0x9F, 0x5E, 0xCA, 0x4E, 0xD4, 0xAC, 0xE5, 0xF3, 0x73, 0xA7, 0x57,
|
||||
0xAF, 0x58, 0xA8, 0x50, 0xF4, 0xEA, 0xD6, 0x74, 0x4F, 0xAE, 0xE9, 0xD5, 0xE7, 0xE6, 0xAD, 0xE8,
|
||||
0x2C, 0xD7, 0x75, 0x7A, 0xEB, 0x16, 0x0B, 0xF5, 0x59, 0xCB, 0x5F, 0xB0, 0x9C, 0xA9, 0x51, 0xA0,
|
||||
0x7F, 0x0C, 0xF6, 0x6F, 0x17, 0xC4, 0x49, 0xEC, 0xD8, 0x43, 0x1F, 0x2D, 0xA4, 0x76, 0x7B, 0xB7,
|
||||
0xCC, 0xBB, 0x3E, 0x5A, 0xFB, 0x60, 0xB1, 0x86, 0x3B, 0x52, 0xA1, 0x6C, 0xAA, 0x55, 0x29, 0x9D,
|
||||
0x97, 0xB2, 0x87, 0x90, 0x61, 0xBE, 0xDC, 0xFC, 0xBC, 0x95, 0xCF, 0xCD, 0x37, 0x3F, 0x5B, 0xD1,
|
||||
0x53, 0x39, 0x84, 0x3C, 0x41, 0xA2, 0x6D, 0x47, 0x14, 0x2A, 0x9E, 0x5D, 0x56, 0xF2, 0xD3, 0xAB,
|
||||
0x44, 0x11, 0x92, 0xD9, 0x23, 0x20, 0x2E, 0x89, 0xB4, 0x7C, 0xB8, 0x26, 0x77, 0x99, 0xE3, 0xA5,
|
||||
0x67, 0x4A, 0xED, 0xDE, 0xC5, 0x31, 0xFE, 0x18, 0x0D, 0x63, 0x8C, 0x80, 0xC0, 0xF7, 0x70, 0x07};
|
||||
|
||||
local MIXTABLE = {
|
||||
0x02, 0x03, 0x01, 0x01,
|
||||
0x01, 0x02, 0x03, 0x01,
|
||||
0x01, 0x01, 0x02, 0x03,
|
||||
0x03, 0x01, 0x01, 0x02};
|
||||
|
||||
local IMIXTABLE = {
|
||||
0x0E, 0x0B, 0x0D, 0x09,
|
||||
0x09, 0x0E, 0x0B, 0x0D,
|
||||
0x0D, 0x09, 0x0E, 0x0B,
|
||||
0x0B, 0x0D, 0x09, 0x0E};
|
||||
|
||||
local RCON = {
|
||||
[0] = 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
||||
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
||||
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
||||
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
||||
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
||||
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
||||
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
||||
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
||||
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
||||
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
||||
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
||||
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
||||
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
||||
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
||||
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
||||
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d};
|
||||
|
||||
|
||||
local GMUL = function(A, B)
|
||||
if(A == 0x01) then return B; end
|
||||
if(B == 0x01) then return A; end
|
||||
if(A == 0x00) then return 0; end
|
||||
if(B == 0x00) then return 0; end
|
||||
|
||||
local LA = LTABLE[A];
|
||||
local LB = LTABLE[B];
|
||||
|
||||
local sum = LA + LB;
|
||||
if (sum > 0xFF) then sum = sum - 0xFF; end
|
||||
|
||||
return ETABLE[sum];
|
||||
end
|
||||
|
||||
local byteSub = Array.substitute;
|
||||
|
||||
local shiftRow = Array.permute;
|
||||
|
||||
local mixCol = function(i, mix)
|
||||
local out = {};
|
||||
|
||||
local a, b, c, d;
|
||||
|
||||
a = GMUL(i[ 1], mix[ 1]);
|
||||
b = GMUL(i[ 2], mix[ 2]);
|
||||
c = GMUL(i[ 3], mix[ 3]);
|
||||
d = GMUL(i[ 4], mix[ 4]);
|
||||
out[ 1] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[ 5]);
|
||||
b = GMUL(i[ 2], mix[ 6]);
|
||||
c = GMUL(i[ 3], mix[ 7]);
|
||||
d = GMUL(i[ 4], mix[ 8]);
|
||||
out[ 2] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[ 9]);
|
||||
b = GMUL(i[ 2], mix[10]);
|
||||
c = GMUL(i[ 3], mix[11]);
|
||||
d = GMUL(i[ 4], mix[12]);
|
||||
out[ 3] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 1], mix[13]);
|
||||
b = GMUL(i[ 2], mix[14]);
|
||||
c = GMUL(i[ 3], mix[15]);
|
||||
d = GMUL(i[ 4], mix[16]);
|
||||
out[ 4] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[ 5], mix[ 1]);
|
||||
b = GMUL(i[ 6], mix[ 2]);
|
||||
c = GMUL(i[ 7], mix[ 3]);
|
||||
d = GMUL(i[ 8], mix[ 4]);
|
||||
out[ 5] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[ 5]);
|
||||
b = GMUL(i[ 6], mix[ 6]);
|
||||
c = GMUL(i[ 7], mix[ 7]);
|
||||
d = GMUL(i[ 8], mix[ 8]);
|
||||
out[ 6] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[ 9]);
|
||||
b = GMUL(i[ 6], mix[10]);
|
||||
c = GMUL(i[ 7], mix[11]);
|
||||
d = GMUL(i[ 8], mix[12]);
|
||||
out[ 7] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 5], mix[13]);
|
||||
b = GMUL(i[ 6], mix[14]);
|
||||
c = GMUL(i[ 7], mix[15]);
|
||||
d = GMUL(i[ 8], mix[16]);
|
||||
out[ 8] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[ 9], mix[ 1]);
|
||||
b = GMUL(i[10], mix[ 2]);
|
||||
c = GMUL(i[11], mix[ 3]);
|
||||
d = GMUL(i[12], mix[ 4]);
|
||||
out[ 9] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[ 5]);
|
||||
b = GMUL(i[10], mix[ 6]);
|
||||
c = GMUL(i[11], mix[ 7]);
|
||||
d = GMUL(i[12], mix[ 8]);
|
||||
out[10] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[ 9]);
|
||||
b = GMUL(i[10], mix[10]);
|
||||
c = GMUL(i[11], mix[11]);
|
||||
d = GMUL(i[12], mix[12]);
|
||||
out[11] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[ 9], mix[13]);
|
||||
b = GMUL(i[10], mix[14]);
|
||||
c = GMUL(i[11], mix[15]);
|
||||
d = GMUL(i[12], mix[16]);
|
||||
out[12] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
|
||||
a = GMUL(i[13], mix[ 1]);
|
||||
b = GMUL(i[14], mix[ 2]);
|
||||
c = GMUL(i[15], mix[ 3]);
|
||||
d = GMUL(i[16], mix[ 4]);
|
||||
out[13] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[ 5]);
|
||||
b = GMUL(i[14], mix[ 6]);
|
||||
c = GMUL(i[15], mix[ 7]);
|
||||
d = GMUL(i[16], mix[ 8]);
|
||||
out[14] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[ 9]);
|
||||
b = GMUL(i[14], mix[10]);
|
||||
c = GMUL(i[15], mix[11]);
|
||||
d = GMUL(i[16], mix[12]);
|
||||
out[15] = XOR(XOR(a, b), XOR(c, d));
|
||||
a = GMUL(i[13], mix[13]);
|
||||
b = GMUL(i[14], mix[14]);
|
||||
c = GMUL(i[15], mix[15]);
|
||||
d = GMUL(i[16], mix[16]);
|
||||
out[16] = XOR(XOR(a, b), XOR(c, d));
|
||||
|
||||
return out;
|
||||
end
|
||||
|
||||
local keyRound = function(key, round)
|
||||
local i = (round - 1) * 32;
|
||||
local out = key;
|
||||
|
||||
out[33 + i] = XOR(key[ 1 + i], XOR(SBOX[key[30 + i]], RCON[round]));
|
||||
out[34 + i] = XOR(key[ 2 + i], SBOX[key[31 + i]]);
|
||||
out[35 + i] = XOR(key[ 3 + i], SBOX[key[32 + i]]);
|
||||
out[36 + i] = XOR(key[ 4 + i], SBOX[key[29 + i]]);
|
||||
|
||||
out[37 + i] = XOR(out[33 + i], key[ 5 + i]);
|
||||
out[38 + i] = XOR(out[34 + i], key[ 6 + i]);
|
||||
out[39 + i] = XOR(out[35 + i], key[ 7 + i]);
|
||||
out[40 + i] = XOR(out[36 + i], key[ 8 + i]);
|
||||
|
||||
out[41 + i] = XOR(out[37 + i], key[ 9 + i]);
|
||||
out[42 + i] = XOR(out[38 + i], key[10 + i]);
|
||||
out[43 + i] = XOR(out[39 + i], key[11 + i]);
|
||||
out[44 + i] = XOR(out[40 + i], key[12 + i]);
|
||||
|
||||
out[45 + i] = XOR(out[41 + i], key[13 + i]);
|
||||
out[46 + i] = XOR(out[42 + i], key[14 + i]);
|
||||
out[47 + i] = XOR(out[43 + i], key[15 + i]);
|
||||
out[48 + i] = XOR(out[44 + i], key[16 + i]);
|
||||
|
||||
|
||||
out[49 + i] = XOR(SBOX[out[45 + i]], key[17 + i]);
|
||||
out[50 + i] = XOR(SBOX[out[46 + i]], key[18 + i]);
|
||||
out[51 + i] = XOR(SBOX[out[47 + i]], key[19 + i]);
|
||||
out[52 + i] = XOR(SBOX[out[48 + i]], key[20 + i]);
|
||||
|
||||
out[53 + i] = XOR(out[49 + i], key[21 + i]);
|
||||
out[54 + i] = XOR(out[50 + i], key[22 + i]);
|
||||
out[55 + i] = XOR(out[51 + i], key[23 + i]);
|
||||
out[56 + i] = XOR(out[52 + i], key[24 + i]);
|
||||
|
||||
out[57 + i] = XOR(out[53 + i], key[25 + i]);
|
||||
out[58 + i] = XOR(out[54 + i], key[26 + i]);
|
||||
out[59 + i] = XOR(out[55 + i], key[27 + i]);
|
||||
out[60 + i] = XOR(out[56 + i], key[28 + i]);
|
||||
|
||||
out[61 + i] = XOR(out[57 + i], key[29 + i]);
|
||||
out[62 + i] = XOR(out[58 + i], key[30 + i]);
|
||||
out[63 + i] = XOR(out[59 + i], key[31 + i]);
|
||||
out[64 + i] = XOR(out[60 + i], key[32 + i]);
|
||||
|
||||
return out;
|
||||
end
|
||||
|
||||
local keyExpand = function(key)
|
||||
local bytes = Array.copy(key);
|
||||
|
||||
for i = 1, 7 do
|
||||
keyRound(bytes, i);
|
||||
end
|
||||
|
||||
local keys = {};
|
||||
|
||||
keys[ 1] = Array.slice(bytes, 1, 16);
|
||||
keys[ 2] = Array.slice(bytes, 17, 32);
|
||||
keys[ 3] = Array.slice(bytes, 33, 48);
|
||||
keys[ 4] = Array.slice(bytes, 49, 64);
|
||||
keys[ 5] = Array.slice(bytes, 65, 80);
|
||||
keys[ 6] = Array.slice(bytes, 81, 96);
|
||||
keys[ 7] = Array.slice(bytes, 97, 112);
|
||||
keys[ 8] = Array.slice(bytes, 113, 128);
|
||||
keys[ 9] = Array.slice(bytes, 129, 144);
|
||||
keys[10] = Array.slice(bytes, 145, 160);
|
||||
keys[11] = Array.slice(bytes, 161, 176);
|
||||
keys[12] = Array.slice(bytes, 177, 192);
|
||||
keys[13] = Array.slice(bytes, 193, 208);
|
||||
keys[14] = Array.slice(bytes, 209, 224);
|
||||
keys[15] = Array.slice(bytes, 225, 240);
|
||||
|
||||
return keys;
|
||||
|
||||
end
|
||||
|
||||
local addKey = Array.XOR;
|
||||
|
||||
|
||||
|
||||
local AES = {};
|
||||
|
||||
AES.blockSize = 16;
|
||||
|
||||
AES.encrypt = function(_key, block)
|
||||
|
||||
local key = keyExpand(_key);
|
||||
|
||||
--round 0
|
||||
block = addKey(block, key[1]);
|
||||
|
||||
--round 1
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[2]);
|
||||
|
||||
--round 2
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[3]);
|
||||
|
||||
--round 3
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[4]);
|
||||
|
||||
--round 4
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[5]);
|
||||
|
||||
--round 5
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[6]);
|
||||
|
||||
--round 6
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[7]);
|
||||
|
||||
--round 7
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[8]);
|
||||
|
||||
--round 8
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[9]);
|
||||
|
||||
--round 9
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[10]);
|
||||
|
||||
--round 10
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[11]);
|
||||
|
||||
--round 11
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[12]);
|
||||
|
||||
--round 12
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[13]);
|
||||
|
||||
--round 13
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = mixCol(block, MIXTABLE);
|
||||
block = addKey(block, key[14]);
|
||||
|
||||
--round 14
|
||||
block = byteSub(block, SBOX);
|
||||
block = shiftRow(block, ROW_SHIFT);
|
||||
block = addKey(block, key[15]);
|
||||
|
||||
return block;
|
||||
|
||||
end
|
||||
|
||||
AES.decrypt = function(_key, block)
|
||||
|
||||
local key = keyExpand(_key);
|
||||
|
||||
--round 0
|
||||
block = addKey(block, key[15]);
|
||||
|
||||
--round 1
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[14]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 2
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[13]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 3
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[12]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 4
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[11]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 5
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[10]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 6
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[9]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 7
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[8]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 8
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[7]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 9
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[6]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 10
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[5]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 11
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[4]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 12
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[3]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 13
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[2]);
|
||||
block = mixCol(block, IMIXTABLE);
|
||||
|
||||
--round 14
|
||||
block = shiftRow(block, IROW_SHIFT);
|
||||
block = byteSub(block, ISBOX);
|
||||
block = addKey(block, key[1]);
|
||||
|
||||
return block;
|
||||
end
|
||||
|
||||
return AES;
|
||||
@@ -1,164 +0,0 @@
|
||||
local Array = require("lockbox.util.array");
|
||||
local Stream = require("lockbox.util.stream");
|
||||
local Queue = require("lockbox.util.queue");
|
||||
|
||||
local CBC = {};
|
||||
|
||||
CBC.Cipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = Array.XOR(iv, block);
|
||||
out = blockCipher.encrypt(key, out);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
iv = out;
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
|
||||
CBC.Decipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = block;
|
||||
out = blockCipher.decrypt(key, out);
|
||||
out = Array.XOR(iv, out);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
iv = block;
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
return CBC;
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
local Array = require("lockbox.util.array");
|
||||
local Stream = require("lockbox.util.stream");
|
||||
local Queue = require("lockbox.util.queue");
|
||||
|
||||
local CFB = {};
|
||||
|
||||
CFB.Cipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = iv;
|
||||
out = blockCipher.encrypt(key, out);
|
||||
out = Array.XOR(out, block);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
iv = out;
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
CFB.Decipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = iv;
|
||||
out = blockCipher.encrypt(key, out);
|
||||
out = Array.XOR(out, block);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
iv = block;
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
return CFB;
|
||||
@@ -1,248 +0,0 @@
|
||||
local Array = require("lockbox.util.array");
|
||||
local Stream = require("lockbox.util.stream");
|
||||
local Queue = require("lockbox.util.queue");
|
||||
|
||||
local Bit = require("lockbox.util.bit");
|
||||
|
||||
local AND = Bit.band;
|
||||
|
||||
local CTR = {};
|
||||
|
||||
CTR.Cipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
local updateIV = function()
|
||||
iv[16] = iv[16] + 1;
|
||||
if iv[16] <= 0xFF then return; end
|
||||
iv[16] = AND(iv[16], 0xFF);
|
||||
|
||||
iv[15] = iv[15] + 1;
|
||||
if iv[15] <= 0xFF then return; end
|
||||
iv[15] = AND(iv[15], 0xFF);
|
||||
|
||||
iv[14] = iv[14] + 1;
|
||||
if iv[14] <= 0xFF then return; end
|
||||
iv[14] = AND(iv[14], 0xFF);
|
||||
|
||||
iv[13] = iv[13] + 1;
|
||||
if iv[13] <= 0xFF then return; end
|
||||
iv[13] = AND(iv[13], 0xFF);
|
||||
|
||||
iv[12] = iv[12] + 1;
|
||||
if iv[12] <= 0xFF then return; end
|
||||
iv[12] = AND(iv[12], 0xFF);
|
||||
|
||||
iv[11] = iv[11] + 1;
|
||||
if iv[11] <= 0xFF then return; end
|
||||
iv[11] = AND(iv[11], 0xFF);
|
||||
|
||||
iv[10] = iv[10] + 1;
|
||||
if iv[10] <= 0xFF then return; end
|
||||
iv[10] = AND(iv[10], 0xFF);
|
||||
|
||||
iv[9] = iv[9] + 1;
|
||||
if iv[9] <= 0xFF then return; end
|
||||
iv[9] = AND(iv[9], 0xFF);
|
||||
|
||||
return;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = iv;
|
||||
out = blockCipher.encrypt(key, out);
|
||||
|
||||
out = Array.XOR(out, block);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
updateIV();
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
|
||||
CTR.Decipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
local updateIV = function()
|
||||
iv[16] = iv[16] + 1;
|
||||
if iv[16] <= 0xFF then return; end
|
||||
iv[16] = AND(iv[16], 0xFF);
|
||||
|
||||
iv[15] = iv[15] + 1;
|
||||
if iv[15] <= 0xFF then return; end
|
||||
iv[15] = AND(iv[15], 0xFF);
|
||||
|
||||
iv[14] = iv[14] + 1;
|
||||
if iv[14] <= 0xFF then return; end
|
||||
iv[14] = AND(iv[14], 0xFF);
|
||||
|
||||
iv[13] = iv[13] + 1;
|
||||
if iv[13] <= 0xFF then return; end
|
||||
iv[13] = AND(iv[13], 0xFF);
|
||||
|
||||
iv[12] = iv[12] + 1;
|
||||
if iv[12] <= 0xFF then return; end
|
||||
iv[12] = AND(iv[12], 0xFF);
|
||||
|
||||
iv[11] = iv[11] + 1;
|
||||
if iv[11] <= 0xFF then return; end
|
||||
iv[11] = AND(iv[11], 0xFF);
|
||||
|
||||
iv[10] = iv[10] + 1;
|
||||
if iv[10] <= 0xFF then return; end
|
||||
iv[10] = AND(iv[10], 0xFF);
|
||||
|
||||
iv[9] = iv[9] + 1;
|
||||
if iv[9] <= 0xFF then return; end
|
||||
iv[9] = AND(iv[9], 0xFF);
|
||||
|
||||
return;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = iv;
|
||||
out = blockCipher.encrypt(key, out);
|
||||
|
||||
out = Array.XOR(out, block);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
updateIV();
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
return CTR;
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
local Array = require("lockbox.util.array");
|
||||
local Stream = require("lockbox.util.stream");
|
||||
local Queue = require("lockbox.util.queue");
|
||||
|
||||
local OFB = {};
|
||||
|
||||
OFB.Cipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = iv;
|
||||
out = blockCipher.encrypt(key, out);
|
||||
iv = out;
|
||||
out = Array.XOR(out, block);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
OFB.Decipher = function()
|
||||
|
||||
local public = {};
|
||||
|
||||
local key;
|
||||
local blockCipher;
|
||||
local padding;
|
||||
local inputQueue;
|
||||
local outputQueue;
|
||||
local iv;
|
||||
|
||||
public.setKey = function(keyBytes)
|
||||
key = keyBytes;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setBlockCipher = function(cipher)
|
||||
blockCipher = cipher;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.setPadding = function(paddingMode)
|
||||
padding = paddingMode;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
inputQueue = Queue();
|
||||
outputQueue = Queue();
|
||||
iv = nil;
|
||||
return public;
|
||||
end
|
||||
|
||||
public.update = function(messageStream)
|
||||
local byte = messageStream();
|
||||
while (byte ~= nil) do
|
||||
inputQueue.push(byte);
|
||||
if(inputQueue.size() >= blockCipher.blockSize) then
|
||||
local block = Array.readFromQueue(inputQueue, blockCipher.blockSize);
|
||||
|
||||
if(iv == nil) then
|
||||
iv = block;
|
||||
else
|
||||
local out = iv;
|
||||
out = blockCipher.encrypt(key, out);
|
||||
iv = out;
|
||||
out = Array.XOR(out, block);
|
||||
Array.writeToQueue(outputQueue, out);
|
||||
end
|
||||
end
|
||||
byte = messageStream();
|
||||
end
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local paddingStream = padding(blockCipher.blockSize, inputQueue.getHead());
|
||||
public.update(paddingStream);
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.getOutputQueue = function()
|
||||
return outputQueue;
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
return Stream.toHex(outputQueue.pop);
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
return Stream.toArray(outputQueue.pop);
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
|
||||
return OFB;
|
||||
201
lockbox/digest/md5.lua
Normal file
201
lockbox/digest/md5.lua
Normal file
@@ -0,0 +1,201 @@
|
||||
require("lockbox").insecure();
|
||||
|
||||
local Bit = require("lockbox.util.bit");
|
||||
local String = require("string");
|
||||
local Math = require("math");
|
||||
local Queue = require("lockbox.util.queue");
|
||||
|
||||
local SHIFT = {
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
|
||||
|
||||
local CONSTANTS = {
|
||||
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
|
||||
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
||||
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
||||
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
||||
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
|
||||
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
||||
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
|
||||
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
||||
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
||||
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
||||
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
|
||||
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
||||
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
|
||||
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
||||
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
||||
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
|
||||
|
||||
local AND = Bit.band;
|
||||
local OR = Bit.bor;
|
||||
local NOT = Bit.bnot;
|
||||
local XOR = Bit.bxor;
|
||||
local LROT = Bit.lrotate;
|
||||
local LSHIFT = Bit.lshift;
|
||||
local RSHIFT = Bit.rshift;
|
||||
|
||||
--MD5 is little-endian
|
||||
local bytes2word = function(b0, b1, b2, b3)
|
||||
local i = b3; i = LSHIFT(i, 8);
|
||||
i = OR(i, b2); i = LSHIFT(i, 8);
|
||||
i = OR(i, b1); i = LSHIFT(i, 8);
|
||||
i = OR(i, b0);
|
||||
return i;
|
||||
end
|
||||
|
||||
local word2bytes = function(word)
|
||||
local b0, b1, b2, b3;
|
||||
b0 = AND(word, 0xFF); word = RSHIFT(word, 8);
|
||||
b1 = AND(word, 0xFF); word = RSHIFT(word, 8);
|
||||
b2 = AND(word, 0xFF); word = RSHIFT(word, 8);
|
||||
b3 = AND(word, 0xFF);
|
||||
return b0, b1, b2, b3;
|
||||
end
|
||||
|
||||
local dword2bytes = function(i)
|
||||
local b4, b5, b6, b7 = word2bytes(Math.floor(i / 0x100000000));
|
||||
local b0, b1, b2, b3 = word2bytes(i);
|
||||
return b0, b1, b2, b3, b4, b5, b6, b7;
|
||||
end
|
||||
|
||||
local F = function(x, y, z) return OR(AND(x, y), AND(NOT(x), z)); end
|
||||
local G = function(x, y, z) return OR(AND(x, z), AND(y, NOT(z))); end
|
||||
local H = function(x, y, z) return XOR(x, XOR(y, z)); end
|
||||
local I = function(x, y, z) return XOR(y, OR(x, NOT(z))); end
|
||||
|
||||
local MD5 = function()
|
||||
|
||||
local queue = Queue();
|
||||
|
||||
local A = 0x67452301;
|
||||
local B = 0xefcdab89;
|
||||
local C = 0x98badcfe;
|
||||
local D = 0x10325476;
|
||||
local public = {};
|
||||
|
||||
local processBlock = function()
|
||||
local a = A;
|
||||
local b = B;
|
||||
local c = C;
|
||||
local d = D;
|
||||
|
||||
local X = {};
|
||||
|
||||
for i = 1, 16 do
|
||||
X[i] = bytes2word(queue.pop(), queue.pop(), queue.pop(), queue.pop());
|
||||
end
|
||||
|
||||
for i = 0, 63 do
|
||||
local f, g, temp;
|
||||
|
||||
if (0 <= i) and (i <= 15) then
|
||||
f = F(b, c, d);
|
||||
g = i;
|
||||
elseif (16 <= i) and (i <= 31) then
|
||||
f = G(b, c, d);
|
||||
g = (5 * i + 1) % 16;
|
||||
elseif (32 <= i) and (i <= 47) then
|
||||
f = H(b, c, d);
|
||||
g = (3 * i + 5) % 16;
|
||||
elseif (48 <= i) and (i <= 63) then
|
||||
f = I(b, c, d);
|
||||
g = (7 * i) % 16;
|
||||
end
|
||||
temp = d;
|
||||
d = c;
|
||||
c = b;
|
||||
b = b + LROT((a + f + CONSTANTS[i + 1] + X[g + 1]), SHIFT[i + 1]);
|
||||
a = temp;
|
||||
end
|
||||
|
||||
A = AND(A + a, 0xFFFFFFFF);
|
||||
B = AND(B + b, 0xFFFFFFFF);
|
||||
C = AND(C + c, 0xFFFFFFFF);
|
||||
D = AND(D + d, 0xFFFFFFFF);
|
||||
end
|
||||
|
||||
public.init = function()
|
||||
queue.reset();
|
||||
|
||||
A = 0x67452301;
|
||||
B = 0xefcdab89;
|
||||
C = 0x98badcfe;
|
||||
D = 0x10325476;
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.update = function(bytes)
|
||||
for b in bytes do
|
||||
queue.push(b);
|
||||
if(queue.size() >= 64) then processBlock(); end
|
||||
end
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.finish = function()
|
||||
local bits = queue.getHead() * 8;
|
||||
|
||||
queue.push(0x80);
|
||||
while ((queue.size() + 7) % 64) < 63 do
|
||||
queue.push(0x00);
|
||||
end
|
||||
|
||||
local b0, b1, b2, b3, b4, b5, b6, b7 = dword2bytes(bits);
|
||||
|
||||
queue.push(b0);
|
||||
queue.push(b1);
|
||||
queue.push(b2);
|
||||
queue.push(b3);
|
||||
queue.push(b4);
|
||||
queue.push(b5);
|
||||
queue.push(b6);
|
||||
queue.push(b7);
|
||||
|
||||
while queue.size() > 0 do
|
||||
processBlock();
|
||||
end
|
||||
|
||||
return public;
|
||||
end
|
||||
|
||||
public.asBytes = function()
|
||||
local b0, b1, b2, b3 = word2bytes(A);
|
||||
local b4, b5, b6, b7 = word2bytes(B);
|
||||
local b8, b9, b10, b11 = word2bytes(C);
|
||||
local b12, b13, b14, b15 = word2bytes(D);
|
||||
|
||||
return {b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15};
|
||||
end
|
||||
|
||||
public.asHex = function()
|
||||
local b0, b1, b2, b3 = word2bytes(A);
|
||||
local b4, b5, b6, b7 = word2bytes(B);
|
||||
local b8, b9, b10, b11 = word2bytes(C);
|
||||
local b12, b13, b14, b15 = word2bytes(D);
|
||||
|
||||
return String.format("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15);
|
||||
end
|
||||
|
||||
public.asString = function()
|
||||
local b0, b1, b2, b3 = word2bytes(A);
|
||||
local b4, b5, b6, b7 = word2bytes(B);
|
||||
local b8, b9, b10, b11 = word2bytes(C);
|
||||
local b12, b13, b14, b15 = word2bytes(D);
|
||||
|
||||
return string.pack(string.rep('B', 16),
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8,
|
||||
b9, b10, b11, b12, b13, b14, b15
|
||||
)
|
||||
end
|
||||
|
||||
return public;
|
||||
|
||||
end
|
||||
|
||||
return MD5;
|
||||
@@ -1,5 +1,8 @@
|
||||
local Lockbox = {};
|
||||
|
||||
-- cc-mek-scada lockbox version
|
||||
Lockbox.version = "1.0"
|
||||
|
||||
--[[
|
||||
package.path = "./?.lua;"
|
||||
.. "./cipher/?.lua;"
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
local ANSIX923Padding = function(blockSize, byteCount)
|
||||
|
||||
local paddingCount = blockSize - (byteCount % blockSize);
|
||||
local bytesLeft = paddingCount;
|
||||
|
||||
local stream = function()
|
||||
if bytesLeft > 1 then
|
||||
bytesLeft = bytesLeft - 1;
|
||||
return 0x00;
|
||||
elseif bytesLeft > 0 then
|
||||
bytesLeft = bytesLeft - 1;
|
||||
return paddingCount;
|
||||
else
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
|
||||
return stream;
|
||||
|
||||
end
|
||||
|
||||
return ANSIX923Padding;
|
||||
@@ -1,22 +0,0 @@
|
||||
local ISOIEC7816Padding = function(blockSize, byteCount)
|
||||
|
||||
local paddingCount = blockSize - (byteCount % blockSize);
|
||||
local bytesLeft = paddingCount;
|
||||
|
||||
local stream = function()
|
||||
if bytesLeft == paddingCount then
|
||||
bytesLeft = bytesLeft - 1;
|
||||
return 0x80;
|
||||
elseif bytesLeft > 0 then
|
||||
bytesLeft = bytesLeft - 1;
|
||||
return 0x00;
|
||||
else
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
|
||||
return stream;
|
||||
|
||||
end
|
||||
|
||||
return ISOIEC7816Padding;
|
||||
@@ -1,18 +0,0 @@
|
||||
local PKCS7Padding = function(blockSize, byteCount)
|
||||
|
||||
local paddingCount = blockSize - ((byteCount -1) % blockSize) + 1;
|
||||
local bytesLeft = paddingCount;
|
||||
|
||||
local stream = function()
|
||||
if bytesLeft > 0 then
|
||||
bytesLeft = bytesLeft - 1;
|
||||
return paddingCount;
|
||||
else
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
|
||||
return stream;
|
||||
end
|
||||
|
||||
return PKCS7Padding;
|
||||
@@ -1,19 +0,0 @@
|
||||
local ZeroPadding = function(blockSize, byteCount)
|
||||
|
||||
local paddingCount = blockSize - ((byteCount -1) % blockSize) + 1;
|
||||
local bytesLeft = paddingCount;
|
||||
|
||||
local stream = function()
|
||||
if bytesLeft > 0 then
|
||||
bytesLeft = bytesLeft - 1;
|
||||
return 0x00;
|
||||
else
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
|
||||
return stream;
|
||||
|
||||
end
|
||||
|
||||
return ZeroPadding;
|
||||
@@ -1,25 +1,19 @@
|
||||
local ok, e
|
||||
ok = nil
|
||||
if not ok then
|
||||
ok, e = pcall(require, "bit") -- the LuaJIT one ?
|
||||
end
|
||||
-- modified (simplified) for ComputerCraft
|
||||
|
||||
local ok, e = nil, nil
|
||||
|
||||
if not ok then
|
||||
ok, e = pcall(require, "bit32") -- Lua 5.2
|
||||
end
|
||||
|
||||
if not ok then
|
||||
ok, e = pcall(require, "bit.numberlua") -- for Lua 5.1, https://github.com/tst2005/lua-bit-numberlua/
|
||||
ok, e = pcall(require, "bit")
|
||||
end
|
||||
|
||||
if not ok then
|
||||
error("no bitwise support found", 2)
|
||||
end
|
||||
|
||||
assert(type(e) == "table", "invalid bit module")
|
||||
|
||||
-- Workaround to support Lua 5.2 bit32 API with the LuaJIT bit one
|
||||
if e.rol and not e.lrotate then
|
||||
e.lrotate = e.rol
|
||||
end
|
||||
if e.ror and not e.rrotate then
|
||||
e.rrotate = e.ror
|
||||
end
|
||||
|
||||
return e
|
||||
|
||||
@@ -10,6 +10,10 @@ config.PKT_CHANNEL = 16244
|
||||
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"
|
||||
|
||||
-- log path
|
||||
config.LOG_PATH = "/log.txt"
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
--
|
||||
-- Core I/O - Pocket Central I/O Management
|
||||
--
|
||||
|
||||
local psil = require("scada-common.psil")
|
||||
|
||||
local coreio = {}
|
||||
|
||||
---@class pocket_core_io
|
||||
local io = {
|
||||
ps = psil.create()
|
||||
}
|
||||
|
||||
---@enum POCKET_LINK_STATE
|
||||
local LINK_STATE = {
|
||||
UNLINKED = 0,
|
||||
SV_LINK_ONLY = 1,
|
||||
API_LINK_ONLY = 2,
|
||||
LINKED = 3
|
||||
}
|
||||
|
||||
coreio.LINK_STATE = LINK_STATE
|
||||
|
||||
-- get the core PSIL
|
||||
function coreio.core_ps()
|
||||
return io.ps
|
||||
end
|
||||
|
||||
-- set network link state
|
||||
---@param state POCKET_LINK_STATE
|
||||
function coreio.report_link_state(state)
|
||||
io.ps.publish("link_state", state)
|
||||
end
|
||||
|
||||
return coreio
|
||||
106
pocket/iocontrol.lua
Normal file
106
pocket/iocontrol.lua
Normal file
@@ -0,0 +1,106 @@
|
||||
--
|
||||
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
||||
--
|
||||
|
||||
local psil = require("scada-common.psil")
|
||||
|
||||
local types = require("scada-common.types")
|
||||
|
||||
local ALARM = types.ALARM
|
||||
|
||||
local iocontrol = {}
|
||||
|
||||
---@class pocket_ioctl
|
||||
local io = {
|
||||
ps = psil.create()
|
||||
}
|
||||
|
||||
---@enum POCKET_LINK_STATE
|
||||
local LINK_STATE = {
|
||||
UNLINKED = 0,
|
||||
SV_LINK_ONLY = 1,
|
||||
API_LINK_ONLY = 2,
|
||||
LINKED = 3
|
||||
}
|
||||
|
||||
---@enum NAV_PAGE
|
||||
local NAV_PAGE = {
|
||||
HOME = 1,
|
||||
UNITS = 2,
|
||||
REACTORS = 3,
|
||||
BOILERS = 4,
|
||||
TURBINES = 5,
|
||||
DIAG = 6,
|
||||
D_ALARMS = 7
|
||||
}
|
||||
|
||||
iocontrol.LINK_STATE = LINK_STATE
|
||||
iocontrol.NAV_PAGE = NAV_PAGE
|
||||
|
||||
-- initialize facility-independent components of pocket iocontrol
|
||||
---@param comms pocket_comms
|
||||
function iocontrol.init_core(comms)
|
||||
---@class pocket_ioctl_diag
|
||||
io.diag = {}
|
||||
|
||||
-- alarm testing
|
||||
io.diag.tone_test = {
|
||||
test_1 = function (state) comms.diag__set_alarm_tone(1, state) end,
|
||||
test_2 = function (state) comms.diag__set_alarm_tone(2, state) end,
|
||||
test_3 = function (state) comms.diag__set_alarm_tone(3, state) end,
|
||||
test_4 = function (state) comms.diag__set_alarm_tone(4, state) end,
|
||||
test_5 = function (state) comms.diag__set_alarm_tone(5, state) end,
|
||||
test_6 = function (state) comms.diag__set_alarm_tone(6, state) end,
|
||||
test_7 = function (state) comms.diag__set_alarm_tone(7, state) end,
|
||||
test_8 = function (state) comms.diag__set_alarm_tone(8, state) end,
|
||||
stop_tones = function () comms.diag__set_alarm_tone(0, false) end,
|
||||
|
||||
test_breach = function (state) comms.diag__set_alarm(ALARM.ContainmentBreach, state) end,
|
||||
test_rad = function (state) comms.diag__set_alarm(ALARM.ContainmentRadiation, state) end,
|
||||
test_lost = function (state) comms.diag__set_alarm(ALARM.ReactorLost, state) end,
|
||||
test_crit = function (state) comms.diag__set_alarm(ALARM.CriticalDamage, state) end,
|
||||
test_dmg = function (state) comms.diag__set_alarm(ALARM.ReactorDamage, state) end,
|
||||
test_overtemp = function (state) comms.diag__set_alarm(ALARM.ReactorOverTemp, state) end,
|
||||
test_hightemp = function (state) comms.diag__set_alarm(ALARM.ReactorHighTemp, state) end,
|
||||
test_wasteleak = function (state) comms.diag__set_alarm(ALARM.ReactorWasteLeak, state) end,
|
||||
test_highwaste = function (state) comms.diag__set_alarm(ALARM.ReactorHighWaste, state) end,
|
||||
test_rps = function (state) comms.diag__set_alarm(ALARM.RPSTransient, state) end,
|
||||
test_rcs = function (state) comms.diag__set_alarm(ALARM.RCSTransient, state) end,
|
||||
test_turbinet = function (state) comms.diag__set_alarm(ALARM.TurbineTrip, state) end,
|
||||
stop_alarms = function () comms.diag__set_alarm(0, false) end,
|
||||
|
||||
get_tone_states = function () comms.diag__get_alarm_tones() end,
|
||||
|
||||
ready_warn = nil, ---@type graphics_element
|
||||
tone_buttons = {},
|
||||
alarm_buttons = {},
|
||||
tone_indicators = {} -- indicators to update from supervisor tone states
|
||||
}
|
||||
|
||||
---@class pocket_nav
|
||||
io.nav = {
|
||||
page = NAV_PAGE.HOME, ---@type NAV_PAGE
|
||||
sub_pages = { NAV_PAGE.HOME, NAV_PAGE.UNITS, NAV_PAGE.REACTORS, NAV_PAGE.BOILERS, NAV_PAGE.TURBINES, NAV_PAGE.DIAG },
|
||||
tasks = {}
|
||||
}
|
||||
|
||||
-- add a task to be performed periodically while on a given page
|
||||
---@param page NAV_PAGE page to add task to
|
||||
---@param task function function to execute
|
||||
function io.nav.register_task(page, task)
|
||||
if io.nav.tasks[page] == nil then io.nav.tasks[page] = {} end
|
||||
table.insert(io.nav.tasks[page], task)
|
||||
end
|
||||
end
|
||||
|
||||
-- initialize facility-dependent components of pocket iocontrol
|
||||
function iocontrol.init_fac() end
|
||||
|
||||
-- set network link state
|
||||
---@param state POCKET_LINK_STATE
|
||||
function iocontrol.report_link_state(state) io.ps.publish("link_state", state) end
|
||||
|
||||
-- get the IO controller database
|
||||
function iocontrol.get_db() return io end
|
||||
|
||||
return iocontrol
|
||||
@@ -1,30 +1,29 @@
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local coreio = require("pocket.coreio")
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local PROTOCOL = comms.PROTOCOL
|
||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
||||
-- local CAPI_TYPE = comms.CAPI_TYPE
|
||||
|
||||
local LINK_STATE = coreio.LINK_STATE
|
||||
local LINK_STATE = iocontrol.LINK_STATE
|
||||
|
||||
local pocket = {}
|
||||
|
||||
-- pocket coordinator + supervisor communications
|
||||
---@nodiscard
|
||||
---@param version string pocket version
|
||||
---@param modem table modem device
|
||||
---@param nic nic network interface device
|
||||
---@param pkt_channel integer pocket comms channel
|
||||
---@param svr_channel integer supervisor access channel
|
||||
---@param crd_channel integer coordinator access channel
|
||||
---@param range integer trusted device connection range
|
||||
---@param sv_watchdog watchdog
|
||||
---@param api_watchdog watchdog
|
||||
function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, range, sv_watchdog, api_watchdog)
|
||||
function pocket.comms(version, nic, pkt_channel, svr_channel, crd_channel, range, sv_watchdog, api_watchdog)
|
||||
local self = {
|
||||
sv = {
|
||||
linked = false,
|
||||
@@ -47,13 +46,9 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(pkt_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
-- configure network channels
|
||||
nic.closeAll()
|
||||
nic.open(pkt_channel)
|
||||
|
||||
-- send a management packet to the supervisor
|
||||
---@param msg_type SCADA_MGMT_TYPE
|
||||
@@ -65,7 +60,7 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv.addr, self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(svr_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, pkt_channel, s_pkt)
|
||||
self.sv.seq_num = self.sv.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -79,24 +74,10 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(crd_channel, pkt_channel, s_pkt)
|
||||
self.api.seq_num = self.api.seq_num + 1
|
||||
end
|
||||
|
||||
-- send a packet to the coordinator API
|
||||
-----@param msg_type CAPI_TYPE
|
||||
-----@param msg table
|
||||
-- local function _send_api(msg_type, msg)
|
||||
-- local s_pkt = comms.scada_packet()
|
||||
-- local pkt = comms.capi_packet()
|
||||
|
||||
-- pkt.make(msg_type, msg)
|
||||
-- s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable())
|
||||
|
||||
-- modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
-- self.api.seq_num = self.api.seq_num + 1
|
||||
-- end
|
||||
|
||||
-- attempt supervisor connection establishment
|
||||
local function _send_sv_establish()
|
||||
_send_sv(SCADA_MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT })
|
||||
@@ -124,13 +105,6 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
---@class pocket_comms
|
||||
local public = {}
|
||||
|
||||
-- reconnect a newly connected modem
|
||||
---@param new_modem table
|
||||
function public.reconnect_modem(new_modem)
|
||||
modem = new_modem
|
||||
_conf_channels()
|
||||
end
|
||||
|
||||
-- close connection to the supervisor
|
||||
function public.close_sv()
|
||||
sv_watchdog.cancel()
|
||||
@@ -158,7 +132,7 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
-- attempt to re-link if any of the dependent links aren't active
|
||||
function public.link_update()
|
||||
if not self.sv.linked then
|
||||
coreio.report_link_state(util.trinary(self.api.linked, LINK_STATE.API_LINK_ONLY, LINK_STATE.UNLINKED))
|
||||
iocontrol.report_link_state(util.trinary(self.api.linked, LINK_STATE.API_LINK_ONLY, LINK_STATE.UNLINKED))
|
||||
|
||||
if self.establish_delay_counter <= 0 then
|
||||
_send_sv_establish()
|
||||
@@ -167,7 +141,7 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
self.establish_delay_counter = self.establish_delay_counter - 1
|
||||
end
|
||||
elseif not self.api.linked then
|
||||
coreio.report_link_state(LINK_STATE.SV_LINK_ONLY)
|
||||
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY)
|
||||
|
||||
if self.establish_delay_counter <= 0 then
|
||||
_send_api_establish()
|
||||
@@ -177,10 +151,29 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
end
|
||||
else
|
||||
-- linked, all good!
|
||||
coreio.report_link_state(LINK_STATE.LINKED)
|
||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
||||
end
|
||||
end
|
||||
|
||||
-- supervisor get active alarm tones
|
||||
function public.diag__get_alarm_tones()
|
||||
if self.sv.linked then _send_sv(SCADA_MGMT_TYPE.DIAG_TONE_GET, {}) end
|
||||
end
|
||||
|
||||
-- supervisor test alarm tones by tone
|
||||
---@param id TONE|0 tone ID, or 0 to stop all
|
||||
---@param state boolean tone state
|
||||
function public.diag__set_alarm_tone(id, state)
|
||||
if self.sv.linked then _send_sv(SCADA_MGMT_TYPE.DIAG_TONE_SET, { id, state }) end
|
||||
end
|
||||
|
||||
-- supervisor test alarm tones by alarm
|
||||
---@param id ALARM|0 alarm ID, 0 to stop all
|
||||
---@param state boolean alarm state
|
||||
function public.diag__set_alarm(id, state)
|
||||
if self.sv.linked then _send_sv(SCADA_MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end
|
||||
end
|
||||
|
||||
-- parse a packet
|
||||
---@param side string
|
||||
---@param sender integer
|
||||
@@ -189,13 +182,10 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
---@param distance integer
|
||||
---@return mgmt_frame|capi_frame|nil packet
|
||||
function public.parse_packet(side, sender, reply_to, message, distance)
|
||||
local s_pkt = nic.receive(side, sender, reply_to, message, distance)
|
||||
local pkt = nil
|
||||
local s_pkt = comms.scada_packet()
|
||||
|
||||
-- parse packet as generic SCADA packet
|
||||
s_pkt.receive(side, sender, reply_to, message, distance)
|
||||
|
||||
if s_pkt.is_valid() then
|
||||
if s_pkt then
|
||||
-- get as SCADA management packet
|
||||
if s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
|
||||
local mgmt_pkt = comms.mgmt_packet()
|
||||
@@ -219,6 +209,8 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
-- handle a packet
|
||||
---@param packet mgmt_frame|capi_frame|nil
|
||||
function public.handle_packet(packet)
|
||||
local diag = iocontrol.get_db().diag
|
||||
|
||||
if packet ~= nil then
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local r_chan = packet.scada_frame.remote_channel()
|
||||
@@ -245,47 +237,9 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
-- feed watchdog on valid sequence number
|
||||
api_watchdog.feed()
|
||||
|
||||
if protocol == PROTOCOL.COORD_API then
|
||||
---@cast packet capi_frame
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
if protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- connection with coordinator established
|
||||
if packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
log.info("coordinator connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.api.linked = true
|
||||
self.api.addr = src_addr
|
||||
|
||||
if self.sv.linked then
|
||||
coreio.report_link_state(LINK_STATE.LINKED)
|
||||
else
|
||||
coreio.report_link_state(LINK_STATE.API_LINK_ONLY)
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||
if self.api.last_est_ack ~= est_ack then
|
||||
log.info("coordinator connection denied")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
if self.api.last_est_ack ~= est_ack then
|
||||
log.info("coordinator connection denied due to collision")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.api.last_est_ack ~= est_ack then
|
||||
log.info("coordinator comms version mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
|
||||
end
|
||||
|
||||
self.api.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("coordinator SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
elseif self.api.linked then
|
||||
if self.api.linked then
|
||||
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||
-- keep alive request received, echo back
|
||||
if packet.length == 1 then
|
||||
@@ -312,6 +266,42 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator")
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- connection with coordinator established
|
||||
if packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
log.info("coordinator connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.api.linked = true
|
||||
self.api.addr = src_addr
|
||||
|
||||
if self.sv.linked then
|
||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
||||
else
|
||||
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||
if self.api.last_est_ack ~= est_ack then
|
||||
log.info("coordinator connection denied")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
if self.api.last_est_ack ~= est_ack then
|
||||
log.info("coordinator connection denied due to collision")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.api.last_est_ack ~= est_ack then
|
||||
log.info("coordinator comms version mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
|
||||
end
|
||||
|
||||
self.api.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("coordinator SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
|
||||
end
|
||||
@@ -339,43 +329,7 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
-- handle packet
|
||||
if protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- connection with supervisor established
|
||||
if packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
log.info("supervisor connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.sv.linked = true
|
||||
self.sv.addr = src_addr
|
||||
|
||||
if self.api.linked then
|
||||
coreio.report_link_state(LINK_STATE.LINKED)
|
||||
else
|
||||
coreio.report_link_state(LINK_STATE.SV_LINK_ONLY)
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||
if self.sv.last_est_ack ~= est_ack then
|
||||
log.info("supervisor connection denied")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
if self.sv.last_est_ack ~= est_ack then
|
||||
log.info("supervisor connection denied due to collision")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.sv.last_est_ack ~= est_ack then
|
||||
log.info("supervisor comms version mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("supervisor SCADA_MGMT establish packet reply unsupported")
|
||||
end
|
||||
|
||||
self.sv.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("supervisor SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
elseif self.sv.linked then
|
||||
if self.sv.linked then
|
||||
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||
-- keep alive request received, echo back
|
||||
if packet.length == 1 then
|
||||
@@ -399,9 +353,90 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
self.sv.r_seq_num = nil
|
||||
self.sv.addr = comms.BROADCAST
|
||||
log.info("supervisor server connection closed by remote host")
|
||||
elseif packet.type == SCADA_MGMT_TYPE.DIAG_TONE_GET then
|
||||
if packet.length == 8 then
|
||||
for i = 1, #packet.data do
|
||||
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
||||
end
|
||||
else
|
||||
log.debug("supervisor SCADA diag alarm states packet length mismatch")
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.DIAG_TONE_SET then
|
||||
if packet.length == 1 and packet.data[1] == false then
|
||||
diag.tone_test.ready_warn.set_value("testing denied")
|
||||
log.debug("supervisor SCADA diag tone set failed")
|
||||
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
||||
local ready = packet.data[1]
|
||||
local states = packet.data[2]
|
||||
|
||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not ready"))
|
||||
|
||||
for i = 1, #states do
|
||||
if diag.tone_test.tone_buttons[i] ~= nil then
|
||||
diag.tone_test.tone_buttons[i].set_value(states[i] == true)
|
||||
diag.tone_test.tone_indicators[i].update(states[i] == true)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug("supervisor SCADA diag tone set packet length/type mismatch")
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.DIAG_ALARM_SET then
|
||||
if packet.length == 1 and packet.data[1] == false then
|
||||
diag.tone_test.ready_warn.set_value("testing denied")
|
||||
log.debug("supervisor SCADA diag alarm set failed")
|
||||
elseif packet.length == 2 and type(packet.data[2]) == "table" then
|
||||
local ready = packet.data[1]
|
||||
local states = packet.data[2]
|
||||
|
||||
diag.tone_test.ready_warn.set_value(util.trinary(ready, "", "system not ready"))
|
||||
|
||||
for i = 1, #states do
|
||||
if diag.tone_test.alarm_buttons[i] ~= nil then
|
||||
diag.tone_test.alarm_buttons[i].set_value(states[i] == true)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- connection with supervisor established
|
||||
if packet.length == 1 then
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
log.info("supervisor connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.sv.linked = true
|
||||
self.sv.addr = src_addr
|
||||
|
||||
if self.api.linked then
|
||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
||||
else
|
||||
iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY)
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||
if self.sv.last_est_ack ~= est_ack then
|
||||
log.info("supervisor connection denied")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
if self.sv.last_est_ack ~= est_ack then
|
||||
log.info("supervisor connection denied due to collision")
|
||||
end
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.sv.last_est_ack ~= est_ack then
|
||||
log.info("supervisor comms version mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("supervisor SCADA_MGMT establish packet reply unsupported")
|
||||
end
|
||||
|
||||
self.sv.last_est_ack = est_ack
|
||||
else
|
||||
log.debug("supervisor SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
else
|
||||
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
|
||||
end
|
||||
|
||||
@@ -4,20 +4,21 @@
|
||||
|
||||
require("/initenv").init_env()
|
||||
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local network = require("scada-common.network")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local config = require("pocket.config")
|
||||
local coreio = require("pocket.coreio")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
local config = require("pocket.config")
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
|
||||
local POCKET_VERSION = "alpha-v0.4.5"
|
||||
local POCKET_VERSION = "v0.6.0-alpha"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -67,7 +68,12 @@ local function main()
|
||||
-- setup communications & clocks
|
||||
----------------------------------------
|
||||
|
||||
coreio.report_link_state(coreio.LINK_STATE.UNLINKED)
|
||||
-- message authentication init
|
||||
if type(config.AUTH_KEY) == "string" then
|
||||
network.init_mac(config.AUTH_KEY)
|
||||
end
|
||||
|
||||
iocontrol.report_link_state(iocontrol.LINK_STATE.UNLINKED)
|
||||
|
||||
-- get the communications modem
|
||||
local modem = ppm.get_wireless_modem()
|
||||
@@ -88,8 +94,9 @@ local function main()
|
||||
|
||||
log.debug("startup> conn watchdogs created")
|
||||
|
||||
-- start comms, open all channels
|
||||
local pocket_comms = pocket.comms(POCKET_VERSION, modem, config.PKT_CHANNEL, config.SVR_CHANNEL,
|
||||
-- create network interface then setup comms
|
||||
local nic = network.nic(modem)
|
||||
local pocket_comms = pocket.comms(POCKET_VERSION, nic, config.PKT_CHANNEL, config.SVR_CHANNEL,
|
||||
config.CRD_CHANNEL, config.TRUSTED_RANGE, conn_wd.sv, conn_wd.api)
|
||||
log.debug("startup> comms init")
|
||||
|
||||
@@ -97,6 +104,9 @@ local function main()
|
||||
local MAIN_CLOCK = 0.5
|
||||
local loop_clock = util.new_clock(MAIN_CLOCK)
|
||||
|
||||
-- init I/O control
|
||||
iocontrol.init_core(pocket_comms)
|
||||
|
||||
----------------------------------------
|
||||
-- start the UI
|
||||
----------------------------------------
|
||||
@@ -105,7 +115,7 @@ local function main()
|
||||
if not ui_ok then
|
||||
renderer.close_ui()
|
||||
println(util.c("UI error: ", message))
|
||||
log.error(util.c("startup> GUI crashed with error ", message))
|
||||
log.error(util.c("startup> GUI render failed with error ", message))
|
||||
else
|
||||
-- start clock
|
||||
loop_clock.start()
|
||||
@@ -121,6 +131,9 @@ local function main()
|
||||
conn_wd.api.feed()
|
||||
log.debug("startup> conn watchdog started")
|
||||
|
||||
local io_db = iocontrol.get_db()
|
||||
local nav = io_db.nav
|
||||
|
||||
-- main event loop
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
@@ -133,6 +146,13 @@ local function main()
|
||||
-- relink if necessary
|
||||
pocket_comms.link_update()
|
||||
|
||||
-- update any tasks for the active page
|
||||
if (type(nav.tasks[nav.page]) == "table") then
|
||||
for i = 1, #nav.tasks[nav.page] do
|
||||
nav.tasks[nav.page][i]()
|
||||
end
|
||||
end
|
||||
|
||||
loop_clock.start()
|
||||
elseif conn_wd.sv.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
-- Pocket GUI Root
|
||||
--
|
||||
|
||||
local coreio = require("pocket.coreio")
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
||||
|
||||
local home_page = require("pocket.ui.pages.home_page")
|
||||
local unit_page = require("pocket.ui.pages.unit_page")
|
||||
local reactor_page = require("pocket.ui.pages.reactor_page")
|
||||
local boiler_page = require("pocket.ui.pages.boiler_page")
|
||||
local diag_page = require("pocket.ui.pages.diag_page")
|
||||
local home_page = require("pocket.ui.pages.home_page")
|
||||
local reactor_page = require("pocket.ui.pages.reactor_page")
|
||||
local turbine_page = require("pocket.ui.pages.turbine_page")
|
||||
local unit_page = require("pocket.ui.pages.unit_page")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
@@ -22,6 +23,9 @@ local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local Sidebar = require("graphics.elements.controls.sidebar")
|
||||
|
||||
local LINK_STATE = iocontrol.LINK_STATE
|
||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
@@ -29,6 +33,9 @@ local cpair = core.cpair
|
||||
-- create new main view
|
||||
---@param main graphics_element main displaybox
|
||||
local function init(main)
|
||||
local nav = iocontrol.get_db().nav
|
||||
local ps = iocontrol.get_db().ps
|
||||
|
||||
-- window header message
|
||||
TextBox{parent=main,y=1,text="",alignment=TEXT_ALIGN.LEFT,height=1,fg_bg=style.header}
|
||||
|
||||
@@ -45,10 +52,10 @@ local function init(main)
|
||||
|
||||
local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes=root_panes}
|
||||
|
||||
root_pane.register(coreio.core_ps(), "link_state", function (state)
|
||||
if state == coreio.LINK_STATE.UNLINKED or state == coreio.LINK_STATE.API_LINK_ONLY then
|
||||
root_pane.register(ps, "link_state", function (state)
|
||||
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
||||
root_pane.set_value(1)
|
||||
elseif state == coreio.LINK_STATE.SV_LINK_ONLY then
|
||||
elseif state == LINK_STATE.SV_LINK_ONLY then
|
||||
root_pane.set_value(2)
|
||||
else
|
||||
root_pane.set_value(3)
|
||||
@@ -81,19 +88,36 @@ local function init(main)
|
||||
{
|
||||
char = "T",
|
||||
color = cpair(colors.black,colors.white)
|
||||
},
|
||||
{
|
||||
char = "D",
|
||||
color = cpair(colors.black,colors.orange)
|
||||
}
|
||||
}
|
||||
|
||||
local pane_1 = home_page(page_div)
|
||||
local pane_2 = unit_page(page_div)
|
||||
local pane_3 = reactor_page(page_div)
|
||||
local pane_4 = boiler_page(page_div)
|
||||
local pane_5 = turbine_page(page_div)
|
||||
local panes = { pane_1, pane_2, pane_3, pane_4, pane_5 }
|
||||
local panes = { home_page(page_div), unit_page(page_div), reactor_page(page_div), boiler_page(page_div), turbine_page(page_div), diag_page(page_div) }
|
||||
|
||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
|
||||
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=page_pane.set_value}
|
||||
local function navigate_sidebar(page)
|
||||
if page == 1 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.HOME]
|
||||
elseif page == 2 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.UNITS]
|
||||
elseif page == 3 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.REACTORS]
|
||||
elseif page == 4 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.BOILERS]
|
||||
elseif page == 5 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.TURBINES]
|
||||
elseif page == 6 then
|
||||
nav.page = nav.sub_pages[NAV_PAGE.DIAG]
|
||||
end
|
||||
|
||||
page_pane.set_value(page)
|
||||
end
|
||||
|
||||
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=navigate_sidebar}
|
||||
end
|
||||
|
||||
return init
|
||||
|
||||
147
pocket/ui/pages/diag_page.lua
Normal file
147
pocket/ui/pages/diag_page.lua
Normal file
@@ -0,0 +1,147 @@
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local MultiPane = require("graphics.elements.multipane")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||
|
||||
local App = require("graphics.elements.controls.app")
|
||||
local Checkbox = require("graphics.elements.controls.checkbox")
|
||||
local PushButton = require("graphics.elements.controls.push_button")
|
||||
local SwitchButton = require("graphics.elements.controls.switch_button")
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
-- new diagnostics page view
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local db = iocontrol.get_db()
|
||||
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
local diag_home = Div{parent=main,x=1,y=1}
|
||||
|
||||
TextBox{parent=diag_home,text="Diagnostic Apps",x=1,y=2,height=1,alignment=TEXT_ALIGN.CENTER}
|
||||
|
||||
local alarm_test = Div{parent=main,x=1,y=1}
|
||||
|
||||
local panes = { diag_home, alarm_test }
|
||||
|
||||
local page_pane = MultiPane{parent=main,x=1,y=1,panes=panes}
|
||||
|
||||
local function navigate_diag()
|
||||
page_pane.set_value(1)
|
||||
db.nav.page = NAV_PAGE.DIAG
|
||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.DIAG
|
||||
end
|
||||
|
||||
local function navigate_alarm()
|
||||
page_pane.set_value(2)
|
||||
db.nav.page = NAV_PAGE.D_ALARMS
|
||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.D_ALARMS
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- Alarm Testing Page --
|
||||
------------------------
|
||||
|
||||
db.nav.register_task(NAV_PAGE.D_ALARMS, db.diag.tone_test.get_tone_states)
|
||||
|
||||
local ttest = db.diag.tone_test
|
||||
|
||||
local c_wht_gray = cpair(colors.white, colors.gray)
|
||||
local c_red_gray = cpair(colors.red, colors.gray)
|
||||
local c_yel_gray = cpair(colors.yellow, colors.gray)
|
||||
local c_blue_gray = cpair(colors.blue, colors.gray)
|
||||
|
||||
local audio = Div{parent=alarm_test,x=1,y=1}
|
||||
|
||||
TextBox{parent=audio,y=1,text="Alarm Sounder Tests",height=1,alignment=TEXT_ALIGN.CENTER}
|
||||
|
||||
ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||
|
||||
PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=navigate_diag}
|
||||
|
||||
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
TextBox{parent=tones,text="Tones",height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||
|
||||
local test_btns = {}
|
||||
test_btns[1] = SwitchButton{parent=tones,text="TEST 1",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_1}
|
||||
test_btns[2] = SwitchButton{parent=tones,text="TEST 2",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_2}
|
||||
test_btns[3] = SwitchButton{parent=tones,text="TEST 3",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_3}
|
||||
test_btns[4] = SwitchButton{parent=tones,text="TEST 4",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_4}
|
||||
test_btns[5] = SwitchButton{parent=tones,text="TEST 5",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_5}
|
||||
test_btns[6] = SwitchButton{parent=tones,text="TEST 6",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_6}
|
||||
test_btns[7] = SwitchButton{parent=tones,text="TEST 7",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_7}
|
||||
test_btns[8] = SwitchButton{parent=tones,text="TEST 8",min_width=8,active_fg_bg=c_wht_gray,callback=ttest.test_8}
|
||||
|
||||
ttest.tone_buttons = test_btns
|
||||
|
||||
local function stop_all_tones()
|
||||
for i = 1, #test_btns do test_btns[i].set_value(false) end
|
||||
ttest.stop_tones()
|
||||
end
|
||||
|
||||
PushButton{parent=tones,text="STOP",min_width=8,active_fg_bg=c_wht_gray,fg_bg=cpair(colors.black,colors.red),callback=stop_all_tones}
|
||||
|
||||
local alarms = Div{parent=audio,x=11,y=3,height=15,fg_bg=cpair(colors.lightGray,colors.black)}
|
||||
|
||||
TextBox{parent=alarms,text="Alarms (\x13)",height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||
|
||||
local alarm_btns = {}
|
||||
alarm_btns[1] = Checkbox{parent=alarms,label="BREACH",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_breach}
|
||||
alarm_btns[2] = Checkbox{parent=alarms,label="RADIATION",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_rad}
|
||||
alarm_btns[3] = Checkbox{parent=alarms,label="RCT LOST",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_lost}
|
||||
alarm_btns[4] = Checkbox{parent=alarms,label="CRIT DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_crit}
|
||||
alarm_btns[5] = Checkbox{parent=alarms,label="DAMAGE",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_dmg}
|
||||
alarm_btns[6] = Checkbox{parent=alarms,label="OVER TEMP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_overtemp}
|
||||
alarm_btns[7] = Checkbox{parent=alarms,label="HIGH TEMP",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_hightemp}
|
||||
alarm_btns[8] = Checkbox{parent=alarms,label="WASTE LEAK",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_wasteleak}
|
||||
alarm_btns[9] = Checkbox{parent=alarms,label="WASTE HIGH",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_highwaste}
|
||||
alarm_btns[10] = Checkbox{parent=alarms,label="RPS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rps}
|
||||
alarm_btns[11] = Checkbox{parent=alarms,label="RCS TRANS",min_width=15,box_fg_bg=c_yel_gray,callback=ttest.test_rcs}
|
||||
alarm_btns[12] = Checkbox{parent=alarms,label="TURBINE TRP",min_width=15,box_fg_bg=c_red_gray,callback=ttest.test_turbinet}
|
||||
|
||||
ttest.alarm_buttons = alarm_btns
|
||||
|
||||
local function stop_all_alarms()
|
||||
for i = 1, #alarm_btns do alarm_btns[i].set_value(false) end
|
||||
ttest.stop_alarms()
|
||||
end
|
||||
|
||||
PushButton{parent=alarms,x=3,y=15,text="STOP \x13",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=c_wht_gray,callback=stop_all_alarms}
|
||||
|
||||
local states = Div{parent=audio,x=2,y=14,height=5,width=8}
|
||||
|
||||
TextBox{parent=states,text="States",height=1,alignment=TEXT_ALIGN.CENTER}
|
||||
local t_1 = IndicatorLight{parent=states,label="1",colors=c_blue_gray}
|
||||
local t_2 = IndicatorLight{parent=states,label="2",colors=c_blue_gray}
|
||||
local t_3 = IndicatorLight{parent=states,label="3",colors=c_blue_gray}
|
||||
local t_4 = IndicatorLight{parent=states,label="4",colors=c_blue_gray}
|
||||
local t_5 = IndicatorLight{parent=states,x=6,y=2,label="5",colors=c_blue_gray}
|
||||
local t_6 = IndicatorLight{parent=states,x=6,label="6",colors=c_blue_gray}
|
||||
local t_7 = IndicatorLight{parent=states,x=6,label="7",colors=c_blue_gray}
|
||||
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
||||
|
||||
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
||||
|
||||
--------------
|
||||
-- App List --
|
||||
--------------
|
||||
|
||||
App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=navigate_alarm,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
||||
App{parent=diag_home,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
||||
App{parent=diag_home,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
return new_view
|
||||
@@ -1,20 +1,21 @@
|
||||
-- local style = require("pocket.ui.style")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local Div = require("graphics.elements.div")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
local App = require("graphics.elements.controls.app")
|
||||
|
||||
-- local cpair = core.cpair
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
local cpair = core.cpair
|
||||
|
||||
-- new home page view
|
||||
---@param root graphics_element parent
|
||||
local function new_view(root)
|
||||
local main = Div{parent=root,x=1,y=1}
|
||||
|
||||
TextBox{parent=main,text="HOME",x=1,y=1,height=1,alignment=TEXT_ALIGN.CENTER}
|
||||
App{parent=main,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)}
|
||||
App{parent=main,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)}
|
||||
App{parent=main,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
App{parent=main,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)}
|
||||
App{parent=main,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
@@ -17,6 +17,10 @@ config.PLC_CHANNEL = 16241
|
||||
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"
|
||||
|
||||
-- log path
|
||||
config.LOG_PATH = "/log.txt"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--
|
||||
-- Main SCADA Coordinator GUI
|
||||
-- Reactor PLC Front Panel GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
@@ -28,7 +28,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
local cpair = core.cpair
|
||||
local border = core.border
|
||||
|
||||
-- create new main view
|
||||
-- create new front panel view
|
||||
---@param panel graphics_element main displaybox
|
||||
local function init(panel)
|
||||
local header = TextBox{parent=panel,y=1,text="REACTOR PLC - UNIT ?",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
|
||||
@@ -12,6 +12,7 @@ local cpair = core.cpair
|
||||
|
||||
-- remap global colors
|
||||
colors.ivory = colors.pink
|
||||
colors.yellow_hc = colors.purple
|
||||
colors.red_off = colors.brown
|
||||
colors.yellow_off = colors.magenta
|
||||
colors.green_off = colors.lime
|
||||
@@ -28,7 +29,7 @@ style.colors = {
|
||||
{ c = colors.cyan, hex = 0x34bac8 },
|
||||
{ c = colors.lightBlue, hex = 0x6cc0f2 },
|
||||
{ c = colors.blue, hex = 0x0096ff },
|
||||
{ c = colors.purple, hex = 0xb156ee },
|
||||
{ c = colors.purple, hex = 0xb156ee }, -- YELLOW HIGH CONTRAST
|
||||
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
|
||||
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
|
||||
-- { c = colors.white, hex = 0xdcd9ca },
|
||||
|
||||
@@ -445,14 +445,14 @@ end
|
||||
---@nodiscard
|
||||
---@param id integer reactor ID
|
||||
---@param version string PLC version
|
||||
---@param modem table modem device
|
||||
---@param nic nic network interface device
|
||||
---@param plc_channel integer PLC comms channel
|
||||
---@param svr_channel integer supervisor server channel
|
||||
---@param range integer trusted device connection range
|
||||
---@param reactor table reactor device
|
||||
---@param rps rps RPS reference
|
||||
---@param conn_watchdog watchdog watchdog reference
|
||||
function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor, rps, conn_watchdog)
|
||||
function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, rps, conn_watchdog)
|
||||
local self = {
|
||||
sv_addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
@@ -470,13 +470,9 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(plc_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
-- configure network channels
|
||||
nic.closeAll()
|
||||
nic.open(plc_channel)
|
||||
|
||||
-- send an RPLC packet
|
||||
---@param msg_type RPLC_TYPE
|
||||
@@ -488,7 +484,7 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
r_pkt.make(id, msg_type, msg)
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(svr_channel, plc_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, plc_channel, s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -502,7 +498,7 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(svr_channel, plc_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, plc_channel, s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -639,7 +635,7 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
|
||||
parallel.waitForAll(table.unpack(tasks))
|
||||
|
||||
if not reactor.__p_is_faulted() then
|
||||
if reactor.__p_is_ok() then
|
||||
_send(RPLC_TYPE.MEK_STRUCT, mek_data)
|
||||
self.resend_build = false
|
||||
end
|
||||
@@ -650,13 +646,6 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
---@class plc_comms
|
||||
local public = {}
|
||||
|
||||
-- reconnect a newly connected modem
|
||||
---@param new_modem table
|
||||
function public.reconnect_modem(new_modem)
|
||||
modem = new_modem
|
||||
_conf_channels()
|
||||
end
|
||||
|
||||
-- reconnect a newly connected reactor
|
||||
---@param new_reactor table
|
||||
function public.reconnect_reactor(new_reactor)
|
||||
@@ -743,13 +732,10 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
---@param distance integer
|
||||
---@return rplc_frame|mgmt_frame|nil packet
|
||||
function public.parse_packet(side, sender, reply_to, message, distance)
|
||||
local s_pkt = nic.receive(side, sender, reply_to, message, distance)
|
||||
local pkt = nil
|
||||
local s_pkt = comms.scada_packet()
|
||||
|
||||
-- parse packet as generic SCADA packet
|
||||
s_pkt.receive(side, sender, reply_to, message, distance)
|
||||
|
||||
if s_pkt.is_valid() then
|
||||
if s_pkt then
|
||||
-- get as RPLC packet
|
||||
if s_pkt.protocol() == PROTOCOL.RPLC then
|
||||
local rplc_pkt = comms.rplc_packet()
|
||||
@@ -836,7 +822,7 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
success = true
|
||||
else
|
||||
reactor.setBurnRate(burn_rate)
|
||||
success = not reactor.__p_is_faulted()
|
||||
success = reactor.__p_is_ok()
|
||||
end
|
||||
else
|
||||
log.debug(burn_rate .. " rate outside of 0 < x <= " .. self.max_burn_rate)
|
||||
@@ -943,47 +929,7 @@ function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor,
|
||||
---@cast packet mgmt_frame
|
||||
-- if linked, only accept packets from configured supervisor
|
||||
if self.linked then
|
||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- link request confirmation
|
||||
if packet.length == 1 then
|
||||
log.debug("received unsolicited establish response")
|
||||
|
||||
local est_ack = packet.data[1]
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
self.status_cache = nil
|
||||
_send_struct()
|
||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
log.debug("re-sent initial status data due to re-establish")
|
||||
else
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
println_ts("received unsolicited link denial, unlinking")
|
||||
log.warning("unsolicited establish request denied")
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
println_ts("received unsolicited link collision, unlinking")
|
||||
log.warning("unsolicited establish request collision")
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
println_ts("received unsolicited link version mismatch, unlinking")
|
||||
log.warning("unsolicited establish request version mismatch")
|
||||
else
|
||||
println_ts("invalid unsolicited link response")
|
||||
log.debug("unsolicited unknown establish request response")
|
||||
end
|
||||
|
||||
-- unlink
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.linked = false
|
||||
end
|
||||
|
||||
-- clear this since this is for something that was unsolicited
|
||||
self.last_est_ack = ESTABLISH_ACK.ALLOW
|
||||
|
||||
-- report link state
|
||||
databus.tx_link_state(est_ack + 1)
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||
-- keep alive request received, echo back
|
||||
if packet.length == 1 and type(packet.data[1]) == "number" then
|
||||
local timestamp = packet.data[1]
|
||||
|
||||
@@ -8,6 +8,7 @@ local comms = require("scada-common.comms")
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local network = require("scada-common.network")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local rsio = require("scada-common.rsio")
|
||||
local util = require("scada-common.util")
|
||||
@@ -18,7 +19,7 @@ local plc = require("reactor-plc.plc")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "v1.4.6"
|
||||
local R_PLC_VERSION = "v1.5.6"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -79,6 +80,11 @@ local function main()
|
||||
-- mount connected devices
|
||||
ppm.mount_all()
|
||||
|
||||
-- message authentication init
|
||||
if type(config.AUTH_KEY) == "string" then
|
||||
network.init_mac(config.AUTH_KEY)
|
||||
end
|
||||
|
||||
-- shared memory across threads
|
||||
---@class plc_shared_memory
|
||||
local __shared_memory = {
|
||||
@@ -88,13 +94,13 @@ local function main()
|
||||
-- PLC system state flags
|
||||
---@class plc_state
|
||||
plc_state = {
|
||||
init_ok = true,
|
||||
fp_ok = false,
|
||||
shutdown = false,
|
||||
degraded = false,
|
||||
init_ok = true,
|
||||
fp_ok = false,
|
||||
shutdown = false,
|
||||
degraded = true,
|
||||
reactor_formed = true,
|
||||
no_reactor = false,
|
||||
no_modem = false
|
||||
no_reactor = true,
|
||||
no_modem = true
|
||||
},
|
||||
|
||||
-- control setpoints
|
||||
@@ -113,6 +119,7 @@ local function main()
|
||||
-- system objects
|
||||
plc_sys = {
|
||||
rps = nil, ---@type rps
|
||||
nic = nil, ---@type nic
|
||||
plc_comms = nil, ---@type plc_comms
|
||||
conn_watchdog = nil ---@type watchdog
|
||||
},
|
||||
@@ -130,14 +137,17 @@ local function main()
|
||||
|
||||
local plc_state = __shared_memory.plc_state
|
||||
|
||||
-- initial state evaluation
|
||||
plc_state.no_reactor = smem_dev.reactor == nil
|
||||
plc_state.no_modem = smem_dev.modem == nil
|
||||
|
||||
-- we need a reactor, can at least do some things even if it isn't formed though
|
||||
if smem_dev.reactor == nil then
|
||||
if plc_state.no_reactor then
|
||||
println("init> fission reactor not found");
|
||||
log.warning("init> no reactor on startup")
|
||||
|
||||
plc_state.init_ok = false
|
||||
plc_state.degraded = true
|
||||
plc_state.no_reactor = true
|
||||
elseif not smem_dev.reactor.isFormed() then
|
||||
println("init> fission reactor not formed");
|
||||
log.warning("init> reactor logic adapter present, but reactor is not formed")
|
||||
@@ -147,7 +157,7 @@ local function main()
|
||||
end
|
||||
|
||||
-- modem is required if networked
|
||||
if __shared_memory.networked and smem_dev.modem == nil then
|
||||
if __shared_memory.networked and plc_state.no_modem then
|
||||
println("init> wireless modem not found")
|
||||
log.warning("init> no wireless modem on startup")
|
||||
|
||||
@@ -158,7 +168,6 @@ local function main()
|
||||
|
||||
plc_state.init_ok = false
|
||||
plc_state.degraded = true
|
||||
plc_state.no_modem = true
|
||||
end
|
||||
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
@@ -181,7 +190,7 @@ local function main()
|
||||
renderer.close_ui()
|
||||
println_ts(util.c("UI error: ", message))
|
||||
println("init> running without front panel")
|
||||
log.error(util.c("GUI crashed with error ", message))
|
||||
log.error(util.c("front panel GUI render failed with error ", message))
|
||||
log.info("init> running in headless mode without front panel")
|
||||
end
|
||||
end
|
||||
@@ -196,8 +205,9 @@ local function main()
|
||||
smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT)
|
||||
log.debug("init> conn watchdog started")
|
||||
|
||||
-- start comms
|
||||
smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_dev.modem, config.PLC_CHANNEL, config.SVR_CHANNEL,
|
||||
-- create network interface then setup comms
|
||||
smem_sys.nic = network.nic(smem_dev.modem)
|
||||
smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_sys.nic, config.PLC_CHANNEL, config.SVR_CHANNEL,
|
||||
config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
|
||||
log.debug("init> comms init")
|
||||
else
|
||||
|
||||
@@ -59,6 +59,7 @@ function threads.thread__main(smem, init)
|
||||
while true do
|
||||
-- get plc_sys fields (may have been set late due to degraded boot)
|
||||
local rps = smem.plc_sys.rps
|
||||
local nic = smem.plc_sys.nic
|
||||
local plc_comms = smem.plc_sys.plc_comms
|
||||
local conn_watchdog = smem.plc_sys.conn_watchdog
|
||||
|
||||
@@ -66,6 +67,7 @@ function threads.thread__main(smem, init)
|
||||
|
||||
-- handle event
|
||||
if event == "timer" and loop_clock.is_clock(param1) then
|
||||
-- note: loop clock is only running if init_ok = true
|
||||
-- blink heartbeat indicator
|
||||
databus.heartbeat()
|
||||
|
||||
@@ -75,7 +77,7 @@ function threads.thread__main(smem, init)
|
||||
loop_clock.start()
|
||||
|
||||
-- send updated data
|
||||
if not plc_state.no_modem then
|
||||
if nic.is_connected() then
|
||||
if plc_comms.is_linked() then
|
||||
smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS)
|
||||
else
|
||||
@@ -114,7 +116,7 @@ function threads.thread__main(smem, init)
|
||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM)
|
||||
|
||||
-- determine if we are still in a degraded state
|
||||
if not networked or not plc_state.no_modem then
|
||||
if (not networked) or nic.is_connected() then
|
||||
plc_state.degraded = false
|
||||
end
|
||||
|
||||
@@ -144,7 +146,7 @@ function threads.thread__main(smem, init)
|
||||
|
||||
-- update indicators
|
||||
databus.tx_hw_status(plc_state)
|
||||
elseif event == "modem_message" and networked and plc_state.init_ok and not plc_state.no_modem then
|
||||
elseif event == "modem_message" and networked and plc_state.init_ok and nic.is_connected() then
|
||||
-- got a packet
|
||||
local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
if packet ~= nil then
|
||||
@@ -163,7 +165,7 @@ function threads.thread__main(smem, init)
|
||||
local type, device = ppm.handle_unmount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "fissionReactorLogicAdapter" then
|
||||
if device == plc_dev.reactor then
|
||||
println_ts("reactor disconnected!")
|
||||
log.error("reactor logic adapter disconnected")
|
||||
|
||||
@@ -171,18 +173,25 @@ function threads.thread__main(smem, init)
|
||||
plc_state.degraded = true
|
||||
elseif networked and type == "modem" then
|
||||
-- we only care if this is our wireless modem
|
||||
if device == plc_dev.modem then
|
||||
if nic.is_modem(device) then
|
||||
nic.disconnect()
|
||||
|
||||
println_ts("comms modem disconnected!")
|
||||
log.error("comms modem disconnected")
|
||||
log.warning("comms modem disconnected")
|
||||
|
||||
plc_state.no_modem = true
|
||||
local other_modem = ppm.get_wireless_modem()
|
||||
if other_modem then
|
||||
log.info("found another wireless modem, using it for comms")
|
||||
nic.connect(other_modem)
|
||||
else
|
||||
plc_state.no_modem = true
|
||||
plc_state.degraded = true
|
||||
|
||||
if plc_state.init_ok then
|
||||
-- try to scram reactor if it is still connected
|
||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
||||
if plc_state.init_ok then
|
||||
-- try to scram reactor if it is still connected
|
||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM)
|
||||
end
|
||||
end
|
||||
|
||||
plc_state.degraded = true
|
||||
else
|
||||
log.warning("non-comms modem disconnected")
|
||||
end
|
||||
@@ -196,15 +205,14 @@ function threads.thread__main(smem, init)
|
||||
local type, device = ppm.mount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "fissionReactorLogicAdapter" then
|
||||
if plc_state.no_reactor and (type == "fissionReactorLogicAdapter") then
|
||||
-- reconnected reactor
|
||||
plc_dev.reactor = device
|
||||
plc_state.no_reactor = false
|
||||
|
||||
println_ts("reactor reconnected.")
|
||||
log.info("reactor reconnected")
|
||||
|
||||
plc_state.no_reactor = false
|
||||
|
||||
-- we need to assume formed here as we cannot check in this main loop
|
||||
-- RPS will identify if it isn't and this will get set false later
|
||||
plc_state.reactor_formed = true
|
||||
@@ -227,22 +235,22 @@ function threads.thread__main(smem, init)
|
||||
rps.reset()
|
||||
end
|
||||
elseif networked and type == "modem" then
|
||||
if device.isWireless() then
|
||||
if device.isWireless() and not nic.is_connected() then
|
||||
-- reconnected modem
|
||||
plc_dev.modem = device
|
||||
plc_state.no_modem = false
|
||||
|
||||
if plc_state.init_ok then
|
||||
plc_comms.reconnect_modem(plc_dev.modem)
|
||||
end
|
||||
if plc_state.init_ok then nic.connect(device) end
|
||||
|
||||
println_ts("wireless modem reconnected.")
|
||||
log.info("comms modem reconnected")
|
||||
plc_state.no_modem = false
|
||||
|
||||
-- determine if we are still in a degraded state
|
||||
if not plc_state.no_reactor then
|
||||
plc_state.degraded = false
|
||||
end
|
||||
elseif device.isWireless() then
|
||||
log.info("unused wireless modem reconnected")
|
||||
else
|
||||
log.info("wired modem reconnected")
|
||||
end
|
||||
@@ -709,9 +717,7 @@ function threads.thread__setpoint_control(smem)
|
||||
end
|
||||
|
||||
-- if ramping completed or was aborted, reset last burn setpoint so that if it is requested again it will be re-attempted
|
||||
if not setpoints.burn_rate_en then
|
||||
last_burn_sp = 0
|
||||
end
|
||||
if not setpoints.burn_rate_en then last_burn_sp = 0 end
|
||||
end
|
||||
|
||||
-- check for termination request
|
||||
|
||||
@@ -10,6 +10,14 @@ config.RTU_CHANNEL = 16242
|
||||
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"
|
||||
|
||||
@@ -37,6 +37,12 @@ function databus.tx_hw_modem(has_modem)
|
||||
databus.ps.publish("has_modem", has_modem)
|
||||
end
|
||||
|
||||
-- transmit the number of speakers connected
|
||||
---@param count integer
|
||||
function databus.tx_hw_spkr_count(count)
|
||||
databus.ps.publish("speaker_count", count)
|
||||
end
|
||||
|
||||
-- transmit unit hardware type across the bus
|
||||
---@param uid integer unit ID
|
||||
---@param type RTU_UNIT_TYPE
|
||||
|
||||
@@ -2,7 +2,7 @@ local rtu = require("rtu.rtu")
|
||||
|
||||
local boilerv_rtu = {}
|
||||
|
||||
-- create new boiler (mek 10.1+) device
|
||||
-- create new boiler device
|
||||
---@nodiscard
|
||||
---@param boiler table
|
||||
---@return rtu_device interface, boolean faulted
|
||||
@@ -10,6 +10,7 @@ function boilerv_rtu.new(boiler)
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- disable auto fault clearing
|
||||
boiler.__p_clear_fault()
|
||||
boiler.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
|
||||
48
rtu/dev/dynamicv_rtu.lua
Normal file
48
rtu/dev/dynamicv_rtu.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
local rtu = require("rtu.rtu")
|
||||
|
||||
local dynamicv_rtu = {}
|
||||
|
||||
-- create new dynamic tank device
|
||||
---@nodiscard
|
||||
---@param dynamic_tank table
|
||||
---@return rtu_device interface, boolean faulted
|
||||
function dynamicv_rtu.new(dynamic_tank)
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- disable auto fault clearing
|
||||
dynamic_tank.__p_clear_fault()
|
||||
dynamic_tank.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
unit.connect_di(dynamic_tank.isFormed)
|
||||
|
||||
-- coils --
|
||||
unit.connect_coil(function () dynamic_tank.incrementContainerEditMode() end, function () end)
|
||||
unit.connect_coil(function () dynamic_tank.decrementContainerEditMode() end, function () end)
|
||||
|
||||
-- input registers --
|
||||
-- multiblock properties
|
||||
unit.connect_input_reg(dynamic_tank.getLength)
|
||||
unit.connect_input_reg(dynamic_tank.getWidth)
|
||||
unit.connect_input_reg(dynamic_tank.getHeight)
|
||||
unit.connect_input_reg(dynamic_tank.getMinPos)
|
||||
unit.connect_input_reg(dynamic_tank.getMaxPos)
|
||||
-- build properties
|
||||
unit.connect_input_reg(dynamic_tank.getTankCapacity)
|
||||
unit.connect_input_reg(dynamic_tank.getChemicalTankCapacity)
|
||||
-- tanks/containers
|
||||
unit.connect_input_reg(dynamic_tank.getStored)
|
||||
unit.connect_input_reg(dynamic_tank.getFilledPercentage)
|
||||
|
||||
-- holding registers --
|
||||
unit.connect_holding_reg(dynamic_tank.getContainerEditMode, dynamic_tank.setContainerEditMode)
|
||||
|
||||
-- check if any calls faulted
|
||||
local faulted = dynamic_tank.__p_is_faulted()
|
||||
dynamic_tank.__p_clear_fault()
|
||||
dynamic_tank.__p_enable_afc()
|
||||
|
||||
return unit.interface(), faulted
|
||||
end
|
||||
|
||||
return dynamicv_rtu
|
||||
@@ -10,6 +10,7 @@ function envd_rtu.new(envd)
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- disable auto fault clearing
|
||||
envd.__p_clear_fault()
|
||||
envd.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
|
||||
@@ -10,6 +10,7 @@ function imatrix_rtu.new(imatrix)
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- disable auto fault clearing
|
||||
imatrix.__p_clear_fault()
|
||||
imatrix.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
|
||||
@@ -10,6 +10,7 @@ function sna_rtu.new(sna)
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- disable auto fault clearing
|
||||
sna.__p_clear_fault()
|
||||
sna.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
|
||||
@@ -10,6 +10,7 @@ function sps_rtu.new(sps)
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- disable auto fault clearing
|
||||
sps.__p_clear_fault()
|
||||
sps.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
|
||||
@@ -2,7 +2,7 @@ local rtu = require("rtu.rtu")
|
||||
|
||||
local turbinev_rtu = {}
|
||||
|
||||
-- create new turbine (mek 10.1+) device
|
||||
-- create new turbine device
|
||||
---@nodiscard
|
||||
---@param turbine table
|
||||
---@return rtu_device interface, boolean faulted
|
||||
@@ -10,6 +10,7 @@ function turbinev_rtu.new(turbine)
|
||||
local unit = rtu.init_unit()
|
||||
|
||||
-- disable auto fault clearing
|
||||
turbine.__p_clear_fault()
|
||||
turbine.__p_disable_afc()
|
||||
|
||||
-- discrete inputs --
|
||||
|
||||
@@ -1,39 +1,31 @@
|
||||
--
|
||||
-- Main SCADA Coordinator GUI
|
||||
-- RTU Front Panel GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("rtu.databus")
|
||||
local databus = require("rtu.databus")
|
||||
|
||||
local style = require("rtu.panel.style")
|
||||
local style = require("rtu.panel.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
|
||||
local LED = require("graphics.elements.indicators.led")
|
||||
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
local LED = require("graphics.elements.indicators.led")
|
||||
local RGBLED = require("graphics.elements.indicators.ledrgb")
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local UNIT_TYPE_LABELS = {
|
||||
"UNKNOWN",
|
||||
"REDSTONE",
|
||||
"BOILER",
|
||||
"TURBINE",
|
||||
"IND MATRIX",
|
||||
"SPS",
|
||||
"SNA",
|
||||
"ENV DETECTOR"
|
||||
}
|
||||
local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC TANK", "IND MATRIX", "SPS", "SNA", "ENV DETECTOR" }
|
||||
|
||||
|
||||
-- create new main view
|
||||
-- create new front panel view
|
||||
---@param panel graphics_element main displaybox
|
||||
---@param units table unit list
|
||||
local function init(panel, units)
|
||||
@@ -71,6 +63,10 @@ local function init(panel, units)
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
|
||||
TextBox{parent=system,x=1,y=14,text="SPEAKERS",height=1,width=8,fg_bg=style.label}
|
||||
local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
speaker_count.register(databus.ps, "speaker_count", speaker_count.update)
|
||||
|
||||
--
|
||||
-- about label
|
||||
--
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user