Compare commits
157 Commits
v1.3.1-bet
...
v1.6.1-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8b926a0978 | ||
|
|
775ffc8094 | ||
|
|
13a8435f6c | ||
|
|
5d6dda5619 | ||
|
|
f8221ad0f1 | ||
|
|
193aeed6df | ||
|
|
8c87cb3e26 | ||
|
|
1548cd706d | ||
|
|
996272e108 | ||
|
|
ef673bdf1b | ||
|
|
7aa236e987 | ||
|
|
35d857a5f4 | ||
|
|
c2c87ec6c6 | ||
|
|
5ce54d78e1 | ||
|
|
c05a312f6c | ||
|
|
86325d9527 | ||
|
|
5074ca89f0 | ||
|
|
c22b048608 | ||
|
|
7ae3014e06 | ||
|
|
8fa37cc9be | ||
|
|
2c730fbdc2 | ||
|
|
5c21140025 | ||
|
|
7859e5ea4c | ||
|
|
0b5ee8eabc | ||
|
|
1decd88415 | ||
|
|
f1b1f0b75a | ||
|
|
f37f2f009f | ||
|
|
15b071378c | ||
|
|
5ba06dcdaf | ||
|
|
f4e7137eb3 | ||
|
|
cf881548d7 | ||
|
|
0a6fd35f93 | ||
|
|
671f8b55bc | ||
|
|
e16b0d237e | ||
|
|
55dab6d675 | ||
|
|
0f5ae9a756 | ||
|
|
cdff7af431 | ||
|
|
c536b823e7 | ||
|
|
b20d42ff38 | ||
|
|
63147bfab5 | ||
|
|
360609df1f | ||
|
|
9a5fc92c86 | ||
|
|
337fca7e7c | ||
|
|
38fc7189ba | ||
|
|
0b939be412 | ||
|
|
351842c9a1 | ||
|
|
8d248408d4 | ||
|
|
2427561dc5 | ||
|
|
b4932b33b6 | ||
|
|
24a7275543 | ||
|
|
529371a0fd | ||
|
|
69df5edbeb | ||
|
|
153a83e569 | ||
|
|
ef1ec220a4 | ||
|
|
8f2e9fe319 | ||
|
|
86ad2a1069 | ||
|
|
494dc437a5 | ||
|
|
deec1ff1df | ||
|
|
4c35233289 | ||
|
|
de9cb3bd3a | ||
|
|
270726e276 | ||
|
|
dbd74afbe6 | ||
|
|
37a91986e5 | ||
|
|
a892c0cf41 | ||
|
|
b7d90872d5 | ||
|
|
f9aa75a105 | ||
|
|
e313b77abc | ||
|
|
ece7c0fe9a | ||
|
|
4aba79f232 | ||
|
|
e159dbb850 | ||
|
|
513c72ea79 | ||
|
|
2c7b98ba42 | ||
|
|
ff9a18a019 | ||
|
|
81005d3e2c |
95
.github/workflows/manifest.yml
vendored
Normal file
95
.github/workflows/manifest.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# Deploy installation manifests and shields versions
|
||||
name: Deploy Installation Data
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- latest
|
||||
- devel
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v3.1.3
|
||||
# Generate manifest + shields files for main branch
|
||||
- name: Checkout main
|
||||
id: checkout-main
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: 'main'
|
||||
clean: false
|
||||
- name: Create outputs folders
|
||||
if: success() || failure()
|
||||
shell: bash
|
||||
run: mkdir deploy; mkdir deploy/manifests; mkdir deploy/manifests/main deploy/manifests/latest deploy/manifests/devel
|
||||
- name: Generate manifest and shields for main branch
|
||||
id: manifest-main
|
||||
if: ${{ (success() || failure()) && steps.checkout-main.outcome == 'success' }}
|
||||
run: python imgen.py shields
|
||||
- name: Save main's manifest
|
||||
if: ${{ (success() || failure()) && steps.manifest-main.outcome == 'success' }}
|
||||
run: mv install_manifest.json deploy/manifests/main
|
||||
# Generate manifest for latest branch
|
||||
- name: Checkout latest
|
||||
id: checkout-latest
|
||||
if: success() || failure()
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: 'latest'
|
||||
clean: false
|
||||
- name: Generate manifest for latest
|
||||
id: manifest-latest
|
||||
if: ${{ (success() || failure()) && steps.checkout-latest.outcome == 'success' }}
|
||||
run: python imgen.py
|
||||
- name: Save latest's manifest
|
||||
if: ${{ (success() || failure()) && steps.manifest-latest.outcome == 'success' }}
|
||||
run: mv install_manifest.json deploy/manifests/latest
|
||||
# Generate manifest for devel branch
|
||||
- name: Checkout devel
|
||||
id: checkout-devel
|
||||
if: success() || failure()
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: 'devel'
|
||||
clean: false
|
||||
- name: Generate manifest for devel
|
||||
id: manifest-devel
|
||||
if: ${{ (success() || failure()) && steps.checkout-devel.outcome == 'success' }}
|
||||
run: python imgen.py
|
||||
- name: Save devel's manifest
|
||||
if: ${{ (success() || failure()) && steps.manifest-devel.outcome == 'success' }}
|
||||
run: mv install_manifest.json deploy/manifests/devel
|
||||
# All artifacts ready now, upload deploy directory
|
||||
- name: Upload artifacts
|
||||
id: upload-artifacts
|
||||
if: ${{ (success() || failure()) && (steps.manifest-main.outcome == 'success' || steps.manifest-latest.outcome == 'success' || steps.manifest-devel.outcome == 'success') }}
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload manifest JSON
|
||||
path: 'deploy/'
|
||||
- name: Deploy to GitHub Pages
|
||||
if: ${{ (success() || failure()) && steps.upload-artifacts.outcome == 'success' }}
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
47
.github/workflows/shields.yml
vendored
47
.github/workflows/shields.yml
vendored
@@ -1,47 +0,0 @@
|
||||
# Simple workflow for deploying static content to GitHub Pages
|
||||
name: Deploy Component Versions
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v3.1.3
|
||||
- run: mkdir shields
|
||||
- run: python imgen.py shields
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
# Upload shields JSON
|
||||
path: 'shields/'
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
_notes/
|
||||
program.sh
|
||||
/*program.sh
|
||||
56
README.md
56
README.md
@@ -7,6 +7,27 @@ 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 +37,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 iUMjgW0C ccmsi.lua`
|
||||
* `pastebin get RGasyTM4 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 +86,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.
|
||||
|
||||
652
ccmsi.lua
652
ccmsi.lua
@@ -20,29 +20,98 @@ 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.0"
|
||||
local CCMSI_VERSION = "v1.7d"
|
||||
|
||||
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_local, v_remote)
|
||||
if v_local ~= nil then
|
||||
if v_local ~= v_remote then
|
||||
print("[" .. name .. "] updating ");blue();print(v_local);white();print(" \xbb ");blue();println(v_remote);white()
|
||||
elseif mode == "install" then
|
||||
pkg_message("[" .. name .. "] reinstalling", v_local)
|
||||
end
|
||||
else
|
||||
pkg_message("[" .. name .. "] new install of", v_remote)
|
||||
end
|
||||
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
|
||||
|
||||
@@ -53,116 +122,138 @@ 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)
|
||||
white();println("<branch>");yellow()
|
||||
println(" second parameter when used with check")
|
||||
term.setTextColor(colors.lightGray)
|
||||
println(" note: defaults to main")
|
||||
println(" target GitHub tag or branch name")
|
||||
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" })
|
||||
if app == nil and mode ~= "check" then
|
||||
println("unrecognized application")
|
||||
red();println("Unrecognized application.");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 repo_path = repo_path .. opts[2] .. "/" else repo_path = repo_path .. "main/" end
|
||||
local install_manifest = repo_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
|
||||
@@ -173,190 +264,95 @@ 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
|
||||
elseif mode == "install" or mode == "update" then
|
||||
-------------------------
|
||||
-- GET REMOTE MANIFEST --
|
||||
-------------------------
|
||||
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
|
||||
local install_manifest = repo_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)
|
||||
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
|
||||
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 },
|
||||
graphics = { v_local = nil, v_remote = nil, changed = false },
|
||||
lockbox = { v_local = nil, v_remote = nil, changed = false }
|
||||
}
|
||||
|
||||
-- try to find local versions
|
||||
local local_ok, local_manifest = read_local_manifest()
|
||||
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)
|
||||
red();println("failed to load local installation information, cannot update");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
|
||||
ver.boot.v_local = local_manifest.versions.bootloader
|
||||
ver.app.v_local = local_manifest.versions[app]
|
||||
ver.comms.v_local = local_manifest.versions.comms
|
||||
ver.graphics.v_local = local_manifest.versions.graphics
|
||||
ver.lockbox.v_local = local_manifest.versions.lockbox
|
||||
|
||||
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)
|
||||
red();println("another application is already installed, please purge it before installing a new application");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)
|
||||
yellow();println("a newer version of the installer is available, it is recommended to download it");white()
|
||||
end
|
||||
end
|
||||
|
||||
local remote_app_version = manifest.versions[app]
|
||||
local remote_comms_version = manifest.versions.comms
|
||||
local remote_boot_version = manifest.versions.bootloader
|
||||
ver.boot.v_remote = manifest.versions.bootloader
|
||||
ver.app.v_remote = manifest.versions[app]
|
||||
ver.comms.v_remote = manifest.versions.comms
|
||||
ver.graphics.v_remote = manifest.versions.graphics
|
||||
ver.lockbox.v_remote = manifest.versions.lockbox
|
||||
|
||||
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)
|
||||
end
|
||||
show_pkg_change("bootldr", ver.boot.v_local, ver.boot.v_remote)
|
||||
ver.boot.changed = ver.boot.v_local ~= ver.boot.v_remote
|
||||
|
||||
-- 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
|
||||
show_pkg_change(app, ver.app.v_local, ver.app.v_remote)
|
||||
ver.app.changed = ver.app.v_local ~= ver.app.v_remote
|
||||
|
||||
-- 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)
|
||||
show_pkg_change("comms", ver.comms.v_local, ver.comms.v_remote)
|
||||
ver.comms.changed = ver.comms.v_local ~= ver.comms.v_remote
|
||||
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
|
||||
|
||||
-- display graphics version change information
|
||||
show_pkg_change("graphics", ver.graphics.v_local, ver.graphics.v_remote)
|
||||
ver.graphics.changed = ver.graphics.v_local ~= ver.graphics.v_remote
|
||||
|
||||
-- display lockbox version change information
|
||||
show_pkg_change("lockbox", ver.lockbox.v_local, ver.lockbox.v_remote)
|
||||
ver.lockbox.changed = ver.lockbox.v_local ~= ver.lockbox.v_remote
|
||||
|
||||
-- ask for confirmation
|
||||
if not ask_y_n("Continue", false) then return end
|
||||
|
||||
--------------------------
|
||||
-- START INSTALL/UPDATE --
|
||||
--------------------------
|
||||
@@ -380,24 +376,27 @@ 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.app.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)
|
||||
@@ -406,28 +405,19 @@ elseif mode == "install" or mode == "update" then
|
||||
|
||||
-- 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("GET HTTP Error " .. err)
|
||||
success = false
|
||||
break
|
||||
else
|
||||
@@ -442,20 +432,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
|
||||
@@ -471,41 +453,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
|
||||
@@ -513,7 +482,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("GET HTTP Error " .. err)
|
||||
success = false
|
||||
break
|
||||
else
|
||||
@@ -527,55 +496,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]
|
||||
@@ -583,9 +540,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")
|
||||
@@ -596,11 +552,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
|
||||
|
||||
@@ -609,10 +563,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
|
||||
|
||||
@@ -621,11 +572,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
|
||||
@@ -633,19 +580,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
|
||||
@@ -658,13 +601,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()
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
local config = {}
|
||||
|
||||
-- port of the SCADA supervisor
|
||||
config.SCADA_SV_PORT = 16100
|
||||
-- port to listen to incoming packets from supervisor
|
||||
config.SCADA_SV_CTL_LISTEN = 16101
|
||||
-- listen port for SCADA coordinator API access
|
||||
config.SCADA_API_LISTEN = 16200
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- coordinator comms channel
|
||||
config.CRD_CHANNEL = 16243
|
||||
-- pocket comms channel
|
||||
config.PKT_CHANNEL = 16244
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
config.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
|
||||
|
||||
@@ -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
|
||||
@@ -183,7 +185,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 +200,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 +216,37 @@ end
|
||||
-- coordinator communications
|
||||
---@nodiscard
|
||||
---@param version string coordinator version
|
||||
---@param modem table modem device
|
||||
---@param sv_port integer port of configured supervisor
|
||||
---@param sv_listen integer listening port for supervisor replys
|
||||
---@param api_listen integer listening port for pocket API
|
||||
---@param nic nic network interface device
|
||||
---@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, sv_port, sv_listen, api_listen, range, sv_watchdog)
|
||||
function coordinator.comms(version, nic, 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(sv_listen)
|
||||
modem.open(api_listen)
|
||||
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
|
||||
@@ -261,23 +264,24 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
end
|
||||
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv_seq_num, protocol, pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.sv_seq_num, protocol, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, crd_channel, s_pkt)
|
||||
self.sv_seq_num = self.sv_seq_num + 1
|
||||
end
|
||||
|
||||
-- send an API establish request response
|
||||
---@param dest integer
|
||||
---@param msg table
|
||||
local function _send_api_establish_ack(seq_id, dest, msg)
|
||||
---@param packet scada_packet
|
||||
---@param ack ESTABLISH_ACK
|
||||
local function _send_api_establish_ack(packet, ack)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg)
|
||||
s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
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(dest, api_listen, s_pkt.raw_sendable())
|
||||
nic.transmit(pkt_channel, crd_channel, s_pkt)
|
||||
self.last_api_est_acks[packet.src_addr()] = ack
|
||||
end
|
||||
|
||||
-- attempt connection establishment
|
||||
@@ -296,82 +300,78 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
---@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
|
||||
function public.close()
|
||||
sv_watchdog.cancel()
|
||||
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 == "modem_message" then
|
||||
-- handle message
|
||||
local packet = public.parse_packet(p1, p2, p3, p4, p5)
|
||||
if packet ~= nil and packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
public.handle_packet(packet)
|
||||
end
|
||||
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
|
||||
@@ -385,7 +385,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
-- 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
|
||||
@@ -398,13 +398,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
---@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()
|
||||
@@ -433,17 +430,25 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
|
||||
-- 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_port = packet.scada_frame.local_port()
|
||||
local r_port = packet.scada_frame.remote_port()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local r_chan = packet.scada_frame.remote_channel()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
if l_port == api_listen then
|
||||
if protocol == PROTOCOL.COORD_API then
|
||||
if l_chan ~= crd_channel then
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
elseif r_chan == pkt_channel 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(r_port)
|
||||
local session = apisessions.find_session(src_addr)
|
||||
|
||||
-- API packet
|
||||
if session ~= nil then
|
||||
@@ -456,7 +461,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- look for an associated session
|
||||
local session = apisessions.find_session(r_port)
|
||||
local session = apisessions.find_session(src_addr)
|
||||
|
||||
-- SCADA management packet
|
||||
if session ~= nil then
|
||||
@@ -464,8 +469,6 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
session.in_queue.push_packet(packet)
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- establish a new session
|
||||
local next_seq_id = packet.scada_frame.seq_num() + 1
|
||||
|
||||
-- validate packet and continue
|
||||
if packet.length == 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||
local comms_v = packet.data[1]
|
||||
@@ -473,42 +476,42 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
if self.last_api_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping API establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
self.last_api_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION
|
||||
end
|
||||
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION })
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||
elseif dev_type == DEVICE_TYPE.PKT then
|
||||
-- pocket linking request
|
||||
local id = apisessions.establish_session(l_port, r_port, firmware_v)
|
||||
println(util.c("API: pocket (", firmware_v, ") [:", r_port, "] connected with session ID ", id))
|
||||
coordinator.log_comms(util.c("API: pocket (", firmware_v, ") [:", r_port, "] connected with session ID ", id))
|
||||
local id = apisessions.establish_session(src_addr, firmware_v)
|
||||
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
||||
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW })
|
||||
self.last_api_est_acks[r_port] = ESTABLISH_ACK.ALLOW
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
else
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on API listening channel"))
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug("invalid establish packet (on API listening channel)")
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_MGMT packet without a known session"))
|
||||
log.debug(util.c("discarding pocket SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||
end
|
||||
else
|
||||
log.debug("illegal packet type " .. protocol .. " on api listening channel", true)
|
||||
log.debug("illegal packet type " .. protocol .. " on pocket channel", true)
|
||||
end
|
||||
elseif l_port == sv_listen then
|
||||
elseif r_chan == svr_channel then
|
||||
-- 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 false
|
||||
else
|
||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
||||
end
|
||||
@@ -570,6 +573,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
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
|
||||
@@ -634,13 +641,46 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH 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
|
||||
local timestamp = packet.data[1]
|
||||
local trip_time = util.time() - timestamp
|
||||
|
||||
if trip_time > 750 then
|
||||
log.warning("coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug("coordinator RTT = " .. trip_time .. "ms")
|
||||
|
||||
iocontrol.get_db().facility.ps.publish("sv_ping", trip_time)
|
||||
|
||||
_send_keep_alive_ack(timestamp)
|
||||
else
|
||||
log.debug("SCADA keep alive packet length mismatch")
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.CLOSE then
|
||||
-- handle session close
|
||||
sv_watchdog.cancel()
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.sv_linked = false
|
||||
self.sv_r_seq_num = nil
|
||||
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 > 1 then
|
||||
-- get configuration
|
||||
|
||||
@@ -659,8 +699,12 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
-- 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("invalid supervisor configuration definitions received, establish failed")
|
||||
@@ -678,14 +722,17 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
|
||||
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
|
||||
@@ -696,34 +743,6 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
elseif self.sv_linked then
|
||||
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
|
||||
-- keep alive request received, echo back
|
||||
if packet.length == 1 then
|
||||
local timestamp = packet.data[1]
|
||||
local trip_time = util.time() - timestamp
|
||||
|
||||
if trip_time > 750 then
|
||||
log.warning("coord KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug("coord RTT = " .. trip_time .. "ms")
|
||||
|
||||
iocontrol.get_db().facility.ps.publish("sv_ping", trip_time)
|
||||
|
||||
_send_keep_alive_ack(timestamp)
|
||||
else
|
||||
log.debug("SCADA keep alive packet length mismatch")
|
||||
end
|
||||
elseif packet.type == SCADA_MGMT_TYPE.CLOSE then
|
||||
-- handle session close
|
||||
sv_watchdog.cancel()
|
||||
self.sv_linked = false
|
||||
println_ts("server connection closed by remote host")
|
||||
log.info("server connection closed by remote host")
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type)
|
||||
end
|
||||
else
|
||||
log.debug("discarding non-link SCADA_MGMT packet before linked")
|
||||
end
|
||||
@@ -731,9 +750,11 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
log.debug("illegal packet type " .. protocol .. " on supervisor listening channel", true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet on unconfigured channel " .. l_port, true)
|
||||
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,6 +33,19 @@ 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
|
||||
@@ -52,6 +71,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,
|
||||
@@ -65,16 +88,21 @@ function iocontrol.init(conf, comms)
|
||||
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)
|
||||
end
|
||||
-- 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, {})
|
||||
|
||||
io.units = {}
|
||||
for i = 1, conf.num_units do
|
||||
@@ -87,11 +115,15 @@ function iocontrol.init(conf, comms)
|
||||
|
||||
num_boilers = 0,
|
||||
num_turbines = 0,
|
||||
num_snas = 0,
|
||||
|
||||
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 +132,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,7 +184,10 @@ function iocontrol.init(conf, comms)
|
||||
boiler_data_tbl = {},
|
||||
|
||||
turbine_ps_tbl = {},
|
||||
turbine_data_tbl = {}
|
||||
turbine_data_tbl = {},
|
||||
|
||||
tank_ps_tbl = {},
|
||||
tank_data_tbl = {}
|
||||
}
|
||||
|
||||
-- create boiler tables
|
||||
@@ -179,6 +214,92 @@ 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 integer unit ID or 0 for main
|
||||
function iocontrol.fp_monitor_state(id, connected)
|
||||
local name = "main_monitor"
|
||||
if id > 0 then name = "unit_monitor_" .. id end
|
||||
io.fp.ps.publish(name, connected)
|
||||
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 +312,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 +378,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 +388,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 +448,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 +496,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 +538,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 +565,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
|
||||
@@ -472,6 +683,9 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
valid = false
|
||||
else
|
||||
local burn_rate_sum = 0.0
|
||||
local sna_count_sum = 0
|
||||
local pu_rate = 0.0
|
||||
local po_rate = 0.0
|
||||
|
||||
-- get all unit statuses
|
||||
for i = 1, #statuses do
|
||||
@@ -480,6 +694,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local unit = io.units[i] ---@type ioctl_unit
|
||||
local status = statuses[i]
|
||||
|
||||
local burn_rate = 0.0
|
||||
|
||||
if type(status) ~= "table" or #status ~= 5 then
|
||||
log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)")
|
||||
valid = false
|
||||
@@ -515,7 +731,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
|
||||
@@ -571,34 +788,21 @@ 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
|
||||
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))
|
||||
@@ -621,36 +825,23 @@ 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
|
||||
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))
|
||||
@@ -662,6 +853,58 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
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", 5) -- active
|
||||
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.unit_ps.publish("sna_prod_rate", unit.sna_prod_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 +982,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,10 +1001,18 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
log.debug(log_header .. "unit state 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
|
||||
pu_rate = pu_rate + util.trinary(is_pu, burn_rate / 10.0, 0.0)
|
||||
po_rate = po_rate + util.trinary(not is_pu, math.min(burn_rate / 10.0, unit.sna_prod_rate), 0.0)
|
||||
end
|
||||
end
|
||||
|
||||
io.facility.ps.publish("burn_sum", burn_rate_sum)
|
||||
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)
|
||||
|
||||
-- update alarm sounder
|
||||
sounder.eval(io.units)
|
||||
@@ -765,6 +1021,8 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
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,12 @@
|
||||
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 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,7 +25,9 @@ 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
|
||||
unit_displays = {}
|
||||
}
|
||||
@@ -46,24 +52,10 @@ end
|
||||
---@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(0, true)
|
||||
for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end
|
||||
end
|
||||
|
||||
-- init all displays in use by the renderer
|
||||
@@ -75,6 +67,17 @@ function renderer.init_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
|
||||
@@ -109,6 +112,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 +164,15 @@ 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 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,12 +185,14 @@ 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
|
||||
for _, display in pairs(engine.ui.unit_displays) do display.delete() end
|
||||
|
||||
-- report ui as not ready
|
||||
engine.ui_ready = false
|
||||
@@ -157,22 +209,121 @@ 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(0, 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(0, 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)
|
||||
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 api = require("coordinator.session.api")
|
||||
local pocket = require("coordinator.session.pocket")
|
||||
|
||||
local apisessions = {}
|
||||
|
||||
local self = {
|
||||
modem = nil,
|
||||
nic = nil,
|
||||
next_id = 0,
|
||||
sessions = {}
|
||||
}
|
||||
@@ -18,7 +19,7 @@ local self = {
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- handle a session output queue
|
||||
---@param session api_session_struct
|
||||
---@param session pkt_session_struct
|
||||
local function _api_handle_outq(session)
|
||||
-- record handler start time
|
||||
local handle_start = util.time()
|
||||
@@ -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(session.r_port, session.l_port, 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
|
||||
@@ -41,15 +42,15 @@ local function _api_handle_outq(session)
|
||||
|
||||
-- max 100ms spent processing queue
|
||||
if util.time() - handle_start > 100 then
|
||||
log.warning("API out queue handler exceeded 100ms queue process limit")
|
||||
log.warning(util.c("offending session: port ", session.r_port))
|
||||
log.warning("[API] out queue handler exceeded 100ms queue process limit")
|
||||
log.warning(util.c("[API] offending session: ", session))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- cleanly close a session
|
||||
---@param session api_session_struct
|
||||
---@param session pkt_session_struct
|
||||
local function _shutdown(session)
|
||||
session.open = false
|
||||
session.instance.close()
|
||||
@@ -58,77 +59,78 @@ 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(session.r_port, session.l_port, msg.message.raw_sendable())
|
||||
self.nic.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message)
|
||||
end
|
||||
end
|
||||
|
||||
log.debug(util.c("closed API session ", session.instance.get_id(), " on remote port ", session.r_port))
|
||||
log.debug(util.c("[API] closed session ", session))
|
||||
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
|
||||
---@nodiscard
|
||||
---@param port integer
|
||||
---@return api_session_struct|nil
|
||||
function apisessions.find_session(port)
|
||||
---@param source_addr integer
|
||||
---@return pkt_session_struct|nil
|
||||
function apisessions.find_session(source_addr)
|
||||
for i = 1, #self.sessions do
|
||||
if self.sessions[i].r_port == port then return self.sessions[i] end
|
||||
if self.sessions[i].s_addr == source_addr then return self.sessions[i] end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- establish a new API session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@param version string
|
||||
---@return integer session_id
|
||||
function apisessions.establish_session(local_port, remote_port, version)
|
||||
---@class api_session_struct
|
||||
local api_s = {
|
||||
function apisessions.establish_session(source_addr, version)
|
||||
---@class pkt_session_struct
|
||||
local pkt_s = {
|
||||
open = true,
|
||||
version = version,
|
||||
l_port = local_port,
|
||||
r_port = remote_port,
|
||||
s_addr = source_addr,
|
||||
in_queue = mqueue.new(),
|
||||
out_queue = mqueue.new(),
|
||||
instance = nil ---@type api_session
|
||||
instance = nil ---@type pkt_session
|
||||
}
|
||||
|
||||
api_s.instance = api.new_session(self.next_id, api_s.in_queue, api_s.out_queue, config.API_TIMEOUT)
|
||||
table.insert(self.sessions, api_s)
|
||||
local id = self.next_id
|
||||
|
||||
log.debug(util.c("established new API session to ", remote_port, " with ID ", self.next_id))
|
||||
pkt_s.instance = pocket.new_session(id, source_addr, pkt_s.in_queue, pkt_s.out_queue, config.API_TIMEOUT)
|
||||
table.insert(self.sessions, pkt_s)
|
||||
|
||||
self.next_id = self.next_id + 1
|
||||
local mt = {
|
||||
---@param s pkt_session_struct
|
||||
__tostring = function (s) return util.c("PKT [", id, "] (@", s.s_addr, ")") end
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
-- success
|
||||
return api_s.instance.get_id()
|
||||
return pkt_s.instance.get_id()
|
||||
end
|
||||
|
||||
-- attempt to identify which session's watchdog timer fired
|
||||
---@param timer_event number
|
||||
function apisessions.check_all_watchdogs(timer_event)
|
||||
for i = 1, #self.sessions do
|
||||
local session = self.sessions[i] ---@type api_session_struct
|
||||
local session = self.sessions[i] ---@type pkt_session_struct
|
||||
if session.open then
|
||||
local triggered = session.instance.check_wd(timer_event)
|
||||
if triggered then
|
||||
log.debug(util.c("watchdog closing API session ", session.instance.get_id(),
|
||||
" on remote port ", session.r_port, "..."))
|
||||
log.debug(util.c("[API] watchdog closing session ", session, "..."))
|
||||
_shutdown(session)
|
||||
end
|
||||
end
|
||||
@@ -138,7 +140,7 @@ end
|
||||
-- iterate all the API sessions
|
||||
function apisessions.iterate_all()
|
||||
for i = 1, #self.sessions do
|
||||
local session = self.sessions[i] ---@type api_session_struct
|
||||
local session = self.sessions[i] ---@type pkt_session_struct
|
||||
|
||||
if session.open and session.instance.iterate() then
|
||||
_api_handle_outq(session)
|
||||
@@ -152,10 +154,9 @@ end
|
||||
function apisessions.free_all_closed()
|
||||
local f = function (session) return session.open end
|
||||
|
||||
---@param session api_session_struct
|
||||
---@param session pkt_session_struct
|
||||
local on_delete = function (session)
|
||||
log.debug(util.c("free'ing closed API session ", session.instance.get_id(),
|
||||
" on remote port ", session.r_port))
|
||||
log.debug(util.c("[API] free'ing closed session ", session))
|
||||
end
|
||||
|
||||
util.filter_table(self.sessions, f, on_delete)
|
||||
@@ -164,7 +165,7 @@ end
|
||||
-- close all open connections
|
||||
function apisessions.close_all()
|
||||
for i = 1, #self.sessions do
|
||||
local session = self.sessions[i] ---@type api_session_struct
|
||||
local session = self.sessions[i] ---@type pkt_session_struct
|
||||
if session.open then _shutdown(session) end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
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 api = {}
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local pocket = {}
|
||||
|
||||
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
|
||||
@@ -21,8 +21,8 @@ local API_S_CMDS = {
|
||||
local API_S_DATA = {
|
||||
}
|
||||
|
||||
api.API_S_CMDS = API_S_CMDS
|
||||
api.API_S_DATA = API_S_DATA
|
||||
pocket.API_S_CMDS = API_S_CMDS
|
||||
pocket.API_S_DATA = API_S_DATA
|
||||
|
||||
local PERIODICS = {
|
||||
KEEP_ALIVE = 2000
|
||||
@@ -31,11 +31,12 @@ local PERIODICS = {
|
||||
-- pocket API session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param s_addr integer device source address
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
function api.new_session(id, in_queue, out_queue, timeout)
|
||||
local log_header = "api_session(" .. id .. "): "
|
||||
function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
local log_header = "pkt_session(" .. id .. "): "
|
||||
|
||||
local self = {
|
||||
-- connection properties
|
||||
@@ -61,13 +62,14 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
---@class api_session
|
||||
---@class pkt_session
|
||||
local public = {}
|
||||
|
||||
-- mark this API session as closed, stop watchdog
|
||||
-- mark this pocket session as closed, stop watchdog
|
||||
local function _close()
|
||||
self.conn_watchdog.cancel()
|
||||
self.connected = false
|
||||
iocontrol.fp_pkt_disconnected(id)
|
||||
end
|
||||
|
||||
-- send a CAPI packet
|
||||
@@ -92,7 +94,7 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
@@ -117,8 +119,6 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
-- process packet
|
||||
if pkt.scada_frame.protocol() == PROTOCOL.COORD_API then
|
||||
---@cast pkt capi_frame
|
||||
-- feed watchdog
|
||||
self.conn_watchdog.feed()
|
||||
|
||||
-- handle packet by type
|
||||
if pkt.type == nil then
|
||||
@@ -136,11 +136,13 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
self.last_rtt = srv_now - srv_start
|
||||
|
||||
if self.last_rtt > 750 then
|
||||
log.warning(log_header .. "API KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||
log.warning(log_header .. "PKT KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug(log_header .. "API RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "API TT = " .. (srv_now - api_send) .. "ms")
|
||||
-- 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
|
||||
@@ -173,7 +175,6 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
function public.close()
|
||||
_close()
|
||||
_send_mgmt(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
println("connection to API session " .. id .. " closed by server")
|
||||
log.info(log_header .. "session closed by server")
|
||||
end
|
||||
|
||||
@@ -212,7 +213,6 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
|
||||
-- exit if connection was closed
|
||||
if not self.connected then
|
||||
println("connection to API session " .. id .. " closed by remote host")
|
||||
log.info(log_header .. "session closed by remote host")
|
||||
return self.connected
|
||||
end
|
||||
@@ -248,4 +248,4 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
return public
|
||||
end
|
||||
|
||||
return api
|
||||
return pocket
|
||||
@@ -4,23 +4,25 @@
|
||||
|
||||
require("/initenv").init_env()
|
||||
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcallbackdsp = require("scada-common.tcallbackdsp")
|
||||
local util = require("scada-common.util")
|
||||
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")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local config = require("coordinator.config")
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local renderer = require("coordinator.renderer")
|
||||
local sounder = require("coordinator.sounder")
|
||||
local config = require("coordinator.config")
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local renderer = require("coordinator.renderer")
|
||||
local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v0.15.2"
|
||||
local COORDINATOR_VERSION = "v0.21.1"
|
||||
|
||||
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
|
||||
@@ -37,9 +39,9 @@ local log_comms_connecting = coordinator.log_comms_connecting
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_port(config.SCADA_SV_PORT)
|
||||
cfv.assert_port(config.SCADA_SV_CTL_LISTEN)
|
||||
cfv.assert_port(config.SCADA_API_LISTEN)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.CRD_CHANNEL)
|
||||
cfv.assert_channel(config.PKT_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.SV_TIMEOUT)
|
||||
cfv.assert_min(config.SV_TIMEOUT, 2)
|
||||
@@ -78,6 +80,9 @@ 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)
|
||||
if not configured or monitors == nil then
|
||||
@@ -125,12 +130,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 +152,7 @@ local function main()
|
||||
return
|
||||
else
|
||||
log_comms("wireless modem connected")
|
||||
iocontrol.fp_has_modem(true)
|
||||
end
|
||||
|
||||
-- create connection watchdog
|
||||
@@ -147,9 +160,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.SCADA_SV_PORT, config.SCADA_SV_CTL_LISTEN,
|
||||
config.SCADA_API_LISTEN, 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.CRD_CHANNEL, config.SVR_CHANNEL,
|
||||
config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog)
|
||||
log.debug("startup> comms init")
|
||||
log_comms("comms initialized")
|
||||
|
||||
@@ -158,78 +172,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.SCADA_SV_PORT)
|
||||
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 +229,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 +266,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 +317,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
|
||||
|
||||
@@ -334,30 +337,24 @@ local function main()
|
||||
apisessions.check_all_watchdogs(param1)
|
||||
|
||||
-- notify timer callback dispatcher
|
||||
tcallbackdsp.handle(param1)
|
||||
tcd.handle(param1)
|
||||
end
|
||||
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 +363,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 +382,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
|
||||
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
@@ -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")
|
||||
@@ -33,7 +35,7 @@ local period = core.flasher.PERIOD
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
local function new_view(root, x, y)
|
||||
assert(root.height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
assert(root.get_height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
|
||||
local facility = iocontrol.get_db().facility
|
||||
local units = iocontrol.get_db().units
|
||||
@@ -43,7 +45,7 @@ local function new_view(root, x, y)
|
||||
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}
|
||||
@@ -52,12 +54,14 @@ local function new_view(root, x, y)
|
||||
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 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=cpair(colors.green,colors.gray)}
|
||||
local sps = IndicatorLight{parent=main,label="SPS Connected",colors=cpair(colors.green,colors.gray)}
|
||||
|
||||
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()
|
||||
|
||||
@@ -99,7 +103,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 +152,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 +230,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 +296,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=cpair(colors.white,colors.gray)}
|
||||
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=cpair(colors.white,colors.gray)}
|
||||
|
||||
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="%12.2f",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
|
||||
@@ -33,41 +33,21 @@ local border = core.border
|
||||
|
||||
local period = core.flasher.PERIOD
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
-- create a unit view
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer
|
||||
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)
|
||||
@@ -398,7 +378,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)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ local function make(parent, x, y, unit)
|
||||
height = 17
|
||||
end
|
||||
|
||||
assert(parent.height() >= (y + height), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
assert(parent.get_height() >= (y + height), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
|
||||
-- bounding box div
|
||||
local root = Div{parent=parent,x=x,y=y,width=80,height=height}
|
||||
|
||||
121
coordinator/ui/layout/front_panel.lua
Normal file
121
coordinator/ui/layout/front_panel.lua
Normal file
@@ -0,0 +1,121 @@
|
||||
--
|
||||
-- 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)
|
||||
|
||||
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")
|
||||
@@ -32,7 +32,7 @@ local function init(main)
|
||||
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=cpair(colors.lightGray, colors.white),width=12,fg_bg=style.header}
|
||||
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
||||
local datetime = TextBox{parent=main,x=(header.width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
|
||||
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}
|
||||
|
||||
ping.register(facility.ps, "sv_ping", ping.update)
|
||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||
@@ -45,12 +45,12 @@ local function init(main)
|
||||
-- unit overviews
|
||||
if facility.num_units >= 1 then
|
||||
uo_1 = unit_overview(main, 2, 3, units[1])
|
||||
row_1_height = uo_1.height()
|
||||
row_1_height = uo_1.get_height()
|
||||
end
|
||||
|
||||
if facility.num_units >= 2 then
|
||||
uo_2 = unit_overview(main, 84, 3, units[2])
|
||||
row_1_height = math.max(row_1_height, uo_2.height())
|
||||
row_1_height = math.max(row_1_height, uo_2.get_height())
|
||||
end
|
||||
|
||||
cnc_y_start = cnc_y_start + row_1_height + 1
|
||||
@@ -60,11 +60,11 @@ local function init(main)
|
||||
local row_2_offset = cnc_y_start
|
||||
|
||||
uo_3 = unit_overview(main, 2, row_2_offset, units[3])
|
||||
cnc_y_start = row_2_offset + uo_3.height() + 1
|
||||
cnc_y_start = row_2_offset + uo_3.get_height() + 1
|
||||
|
||||
if facility.num_units == 4 then
|
||||
uo_4 = unit_overview(main, 84, row_2_offset, units[4])
|
||||
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.height() + 1)
|
||||
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,11 +73,11 @@ local function init(main)
|
||||
cnc_y_start = cnc_y_start
|
||||
|
||||
-- induction matrix and process control interfaces are 24 tall + space needed for divider
|
||||
local cnc_bottom_align_start = main.height() - 26
|
||||
local cnc_bottom_align_start = main.get_height() - 26
|
||||
|
||||
assert(cnc_bottom_align_start >= cnc_y_start, "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
|
||||
TextBox{parent=main,y=cnc_bottom_align_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)}
|
||||
TextBox{parent=main,y=cnc_bottom_align_start,text=util.strrep("\x8c", header.get_width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)}
|
||||
|
||||
cnc_bottom_align_start = cnc_bottom_align_start + 2
|
||||
|
||||
|
||||
58
coordinator/ui/pgi.lua
Normal file
58
coordinator/ui/pgi.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
--
|
||||
-- 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
|
||||
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)
|
||||
@@ -151,7 +186,90 @@ 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.black, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ local flasher = require("graphics.flasher")
|
||||
|
||||
local core = {}
|
||||
|
||||
core.version = "1.0.1"
|
||||
|
||||
core.flasher = flasher
|
||||
core.events = events
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ local element = {}
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer next line if omitted
|
||||
---@field offset_x? integer 0 if omitted
|
||||
---@field offset_y? integer 0 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
---@alias graphics_args graphics_args_generic
|
||||
---|waiting_args
|
||||
---|checkbox_args
|
||||
---|hazard_button_args
|
||||
---|multi_button_args
|
||||
---|push_button_args
|
||||
@@ -46,6 +46,7 @@ local element = {}
|
||||
---|colormap_args
|
||||
---|displaybox_args
|
||||
---|div_args
|
||||
---|listbox_args
|
||||
---|multipane_args
|
||||
---|pipenet_args
|
||||
---|rectangle_args
|
||||
@@ -60,28 +61,30 @@ local element = {}
|
||||
-- a base graphics element, should not be created on its own
|
||||
---@nodiscard
|
||||
---@param args graphics_args arguments
|
||||
function element.new(args)
|
||||
---@param child_offset_x? integer mouse event offset x
|
||||
---@param child_offset_y? integer mouse event offset y
|
||||
function element.new(args, child_offset_x, child_offset_y)
|
||||
local self = {
|
||||
id = -1,
|
||||
id = nil, ---@type element_id|nil
|
||||
elem_type = debug.getinfo(2).name,
|
||||
define_completed = false,
|
||||
p_window = nil, ---@type table
|
||||
position = { x = 1, y = 1 }, ---@type coordinate_2d
|
||||
child_offset = { x = 0, y = 0 },
|
||||
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
|
||||
next_y = 1,
|
||||
children = {},
|
||||
subscriptions = {},
|
||||
mt = {}
|
||||
}
|
||||
|
||||
---@class graphics_template
|
||||
---@class graphics_base
|
||||
local protected = {
|
||||
enabled = true,
|
||||
value = nil, ---@type any
|
||||
window = nil, ---@type table
|
||||
value = nil, ---@type any
|
||||
window = nil, ---@type table
|
||||
content_window = nil, ---@type table|nil
|
||||
fg_bg = core.cpair(colors.white, colors.black),
|
||||
frame = core.gframe(1, 1, 1, 1)
|
||||
frame = core.gframe(1, 1, 1, 1),
|
||||
children = {}
|
||||
}
|
||||
|
||||
local name_brief = "graphics.element{" .. self.elem_type .. "}: "
|
||||
@@ -101,8 +104,8 @@ function element.new(args)
|
||||
-------------------------
|
||||
|
||||
-- prepare the template
|
||||
---@param offset_x integer x offset
|
||||
---@param offset_y integer y offset
|
||||
---@param offset_x integer x offset for mouse events
|
||||
---@param offset_y integer y offset for mouse events
|
||||
---@param next_y integer next line if no y was provided
|
||||
function protected.prepare_template(offset_x, offset_y, next_y)
|
||||
-- get frame coordinates/size
|
||||
@@ -114,36 +117,18 @@ function element.new(args)
|
||||
else
|
||||
local w, h = self.p_window.getSize()
|
||||
protected.frame.x = args.x or 1
|
||||
|
||||
if args.parent ~= nil then
|
||||
protected.frame.y = args.y or (next_y - offset_y)
|
||||
else
|
||||
protected.frame.y = args.y or next_y
|
||||
end
|
||||
|
||||
protected.frame.y = args.y or next_y
|
||||
protected.frame.w = args.width or w
|
||||
protected.frame.h = args.height or h
|
||||
end
|
||||
|
||||
-- inner offsets
|
||||
if args.offset_x ~= nil then self.child_offset.x = args.offset_x end
|
||||
if args.offset_y ~= nil then self.child_offset.y = args.offset_y end
|
||||
|
||||
-- adjust window frame if applicable
|
||||
local f = protected.frame
|
||||
local x = f.x
|
||||
local y = f.y
|
||||
|
||||
-- apply offsets
|
||||
if args.parent ~= nil then
|
||||
-- constrain to parent inner width/height
|
||||
local w, h = self.p_window.getSize()
|
||||
f.w = math.min(f.w, w - ((2 * offset_x) + (f.x - 1)))
|
||||
f.h = math.min(f.h, h - ((2 * offset_y) + (f.y - 1)))
|
||||
|
||||
-- offset x/y
|
||||
f.x = x + offset_x
|
||||
f.y = y + offset_y
|
||||
f.w = math.min(f.w, w - (f.x - 1))
|
||||
f.h = math.min(f.h, h - (f.y - 1))
|
||||
end
|
||||
|
||||
-- check frame
|
||||
@@ -153,7 +138,7 @@ function element.new(args)
|
||||
assert(f.h >= 1, name_brief .. "frame height not >= 1")
|
||||
|
||||
-- create window
|
||||
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true)
|
||||
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, args.hidden ~= true)
|
||||
|
||||
-- init colors
|
||||
if args.fg_bg ~= nil then
|
||||
@@ -170,7 +155,11 @@ function element.new(args)
|
||||
-- record position
|
||||
self.position.x, self.position.y = protected.window.getPosition()
|
||||
|
||||
-- calculate bounds
|
||||
-- shift per parent child offset
|
||||
self.position.x = self.position.x + offset_x
|
||||
self.position.y = self.position.y + offset_y
|
||||
|
||||
-- calculate mouse event bounds
|
||||
self.bounds.x1 = self.position.x
|
||||
self.bounds.x2 = self.position.x + f.w - 1
|
||||
self.bounds.y1 = self.position.y
|
||||
@@ -198,15 +187,15 @@ function element.new(args)
|
||||
-- luacheck: push ignore
|
||||
---@diagnostic disable: unused-local, unused-vararg
|
||||
|
||||
-- dynamically insert a child element
|
||||
---@param id string|integer element identifier
|
||||
---@param elem graphics_element element
|
||||
function protected.insert(id, elem)
|
||||
-- handle a child element having been added
|
||||
---@param id element_id element identifier
|
||||
---@param child graphics_element child element
|
||||
function protected.on_added(id, child)
|
||||
end
|
||||
|
||||
-- dynamically remove a child element
|
||||
---@param id string|integer element identifier
|
||||
function protected.remove(id)
|
||||
-- handle a child element having been removed
|
||||
---@param id element_id element identifier
|
||||
function protected.on_removed(id)
|
||||
end
|
||||
|
||||
-- handle a mouse event
|
||||
@@ -279,6 +268,14 @@ function element.new(args)
|
||||
---@return graphics_element element, element_id id
|
||||
function protected.get() return public, self.id end
|
||||
|
||||
-- report completion of element instantiation and get the public interface
|
||||
---@nodiscard
|
||||
---@return graphics_element element, element_id id
|
||||
function protected.complete()
|
||||
if args.parent ~= nil then args.parent.__child_ready(self.id, public) end
|
||||
return public, self.id
|
||||
end
|
||||
|
||||
-----------
|
||||
-- SETUP --
|
||||
-----------
|
||||
@@ -294,6 +291,7 @@ function element.new(args)
|
||||
|
||||
-- prepare the template
|
||||
if args.parent == nil then
|
||||
self.id = args.id or "__ROOT__"
|
||||
protected.prepare_template(0, 0, 1)
|
||||
else
|
||||
self.id = args.parent.__add_child(args.id, protected)
|
||||
@@ -305,11 +303,21 @@ function element.new(args)
|
||||
|
||||
-- get the window object
|
||||
---@nodiscard
|
||||
function public.window() return protected.window end
|
||||
function public.window() return protected.content_window or protected.window end
|
||||
|
||||
-- delete this element (hide and unsubscribe from PSIL)
|
||||
function public.delete()
|
||||
-- hide + stop animations
|
||||
local fg_bg = protected.fg_bg
|
||||
|
||||
if args.parent ~= nil then
|
||||
-- grab parent fg/bg so we can clear cleanly as a child element
|
||||
fg_bg = args.parent.get_fg_bg()
|
||||
end
|
||||
|
||||
-- clear, hide, and stop animations
|
||||
protected.window.setBackgroundColor(fg_bg.bkg)
|
||||
protected.window.setTextColor(fg_bg.fgd)
|
||||
protected.window.clear()
|
||||
public.hide()
|
||||
|
||||
-- unsubscribe from PSIL
|
||||
@@ -319,9 +327,14 @@ function element.new(args)
|
||||
end
|
||||
|
||||
-- delete all children
|
||||
for k, v in pairs(self.children) do
|
||||
for k, v in pairs(protected.children) do
|
||||
v.delete()
|
||||
self.children[k] = nil
|
||||
protected.children[k] = nil
|
||||
end
|
||||
|
||||
if args.parent ~= nil then
|
||||
-- remove self from parent
|
||||
args.parent.__remove_child(self.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -330,41 +343,53 @@ function element.new(args)
|
||||
-- add a child element
|
||||
---@nodiscard
|
||||
---@param key string|nil id
|
||||
---@param child graphics_template
|
||||
---@param child graphics_base
|
||||
---@return integer|string key
|
||||
function public.__add_child(key, child)
|
||||
-- offset first automatic placement
|
||||
if self.next_y <= self.child_offset.y then
|
||||
self.next_y = self.child_offset.y + 1
|
||||
end
|
||||
|
||||
child.prepare_template(self.child_offset.x, self.child_offset.y, self.next_y)
|
||||
child.prepare_template(child_offset_x or 0, child_offset_y or 0, self.next_y)
|
||||
|
||||
self.next_y = child.frame.y + child.frame.h
|
||||
|
||||
local child_element = child.get()
|
||||
|
||||
if key == nil then
|
||||
table.insert(self.children, child_element)
|
||||
return #self.children
|
||||
table.insert(protected.children, child_element)
|
||||
return #protected.children
|
||||
else
|
||||
self.children[key] = child_element
|
||||
protected.children[key] = child_element
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
-- remove a child element
|
||||
---@param key element_id id
|
||||
function public.__remove_child(key)
|
||||
if protected.children[key] ~= nil then
|
||||
protected.on_removed(key)
|
||||
protected.children[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- actions to take upon a child element becoming ready (initial draw/construction completed)
|
||||
---@param key element_id id
|
||||
---@param child graphics_element
|
||||
function public.__child_ready(key, child)
|
||||
protected.on_added(key, child)
|
||||
end
|
||||
|
||||
-- get a child element
|
||||
---@nodiscard
|
||||
---@param id element_id
|
||||
---@return graphics_element
|
||||
function public.get_child(id) return self.children[id] end
|
||||
function public.get_child(id) return protected.children[id] end
|
||||
|
||||
-- remove a child element
|
||||
---@param id element_id
|
||||
function public.remove(id)
|
||||
if self.children[id] ~= nil then
|
||||
self.children[id].delete()
|
||||
self.children[id] = nil
|
||||
if protected.children[id] ~= nil then
|
||||
protected.children[id].delete()
|
||||
protected.on_removed(id)
|
||||
protected.children[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -373,37 +398,18 @@ function element.new(args)
|
||||
---@param id element_id
|
||||
---@return graphics_element|nil element
|
||||
function public.get_element_by_id(id)
|
||||
if self.children[id] == nil then
|
||||
for _, child in pairs(self.children) do
|
||||
if protected.children[id] == nil then
|
||||
for _, child in pairs(protected.children) do
|
||||
local elem = child.get_element_by_id(id)
|
||||
if elem ~= nil then return elem end
|
||||
end
|
||||
else
|
||||
return self.children[id]
|
||||
return protected.children[id]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- DYNAMIC CHILD ELEMENTS --
|
||||
|
||||
-- insert an element as a contained child<br>
|
||||
-- this is intended to be used dynamically, and depends on the target element type.<br>
|
||||
-- not all elements support dynamic children.
|
||||
---@param id string|integer element identifier
|
||||
---@param elem graphics_element element
|
||||
function public.insert_element(id, elem)
|
||||
protected.insert(id, elem)
|
||||
end
|
||||
|
||||
-- remove an element from contained children<br>
|
||||
-- this is intended to be used dynamically, and depends on the target element type.<br>
|
||||
-- not all elements support dynamic children.
|
||||
---@param id string|integer element identifier
|
||||
function public.remove_element(id)
|
||||
protected.remove(id)
|
||||
end
|
||||
|
||||
-- AUTO-PLACEMENT --
|
||||
|
||||
-- skip a line for automatically placed elements
|
||||
@@ -437,14 +443,14 @@ function element.new(args)
|
||||
-- get element width
|
||||
---@nodiscard
|
||||
---@return integer width
|
||||
function public.width()
|
||||
function public.get_width()
|
||||
return protected.frame.w
|
||||
end
|
||||
|
||||
-- get element height
|
||||
---@nodiscard
|
||||
---@return integer height
|
||||
function public.height()
|
||||
function public.get_height()
|
||||
return protected.frame.h
|
||||
end
|
||||
|
||||
@@ -519,7 +525,7 @@ function element.new(args)
|
||||
|
||||
-- handle the mouse event then pass to children
|
||||
protected.handle_mouse(event_T)
|
||||
for _, child in pairs(self.children) do child.handle_mouse(event_T) end
|
||||
for _, child in pairs(protected.children) do child.handle_mouse(event_T) end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -545,27 +551,61 @@ function element.new(args)
|
||||
ps.subscribe(key, func)
|
||||
end
|
||||
|
||||
-- VISIBILITY --
|
||||
-- VISIBILITY & ANIMATIONS --
|
||||
|
||||
-- show the element
|
||||
function public.show()
|
||||
-- show the element and enables animations by default
|
||||
---@param animate? boolean true (default) to automatically resume animations
|
||||
function public.show(animate)
|
||||
protected.window.setVisible(true)
|
||||
protected.start_anim()
|
||||
for _, child in pairs(self.children) do child.show() end
|
||||
if animate ~= false then public.animate_all() end
|
||||
end
|
||||
|
||||
-- hide the element
|
||||
-- hide the element and disables animations<br>
|
||||
-- this alone does not cause an element to be fully hidden, it only prevents updates from being shown<br>
|
||||
---@see graphics_element.content_redraw
|
||||
function public.hide()
|
||||
protected.stop_anim()
|
||||
for _, child in pairs(self.children) do child.hide() end
|
||||
public.freeze_all() -- stop animations for efficiency/performance
|
||||
protected.window.setVisible(false)
|
||||
end
|
||||
|
||||
-- start/resume animation(s)
|
||||
function public.animate()
|
||||
protected.start_anim()
|
||||
end
|
||||
|
||||
-- start/resume animation(s) for this element and all its children<br>
|
||||
-- only animates if a window is visible
|
||||
function public.animate_all()
|
||||
if protected.window.isVisible() then
|
||||
public.animate()
|
||||
for _, child in pairs(protected.children) do child.animate_all() end
|
||||
end
|
||||
end
|
||||
|
||||
-- freeze animation(s)
|
||||
function public.freeze()
|
||||
protected.stop_anim()
|
||||
end
|
||||
|
||||
-- freeze animation(s) for this element and all its children
|
||||
function public.freeze_all()
|
||||
public.freeze()
|
||||
for _, child in pairs(protected.children) do child.freeze_all() end
|
||||
end
|
||||
|
||||
-- re-draw the element
|
||||
function public.redraw()
|
||||
protected.window.redraw()
|
||||
end
|
||||
|
||||
-- if a content window is set, clears it then re-draws all children
|
||||
function public.content_redraw()
|
||||
if protected.content_window ~= nil then
|
||||
protected.content_window.clear()
|
||||
for _, child in pairs(protected.children) do child.redraw() end
|
||||
end
|
||||
end
|
||||
|
||||
return protected
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Loading/Waiting Animation Graphics Element
|
||||
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local tcd = require("scada-common.tcd")
|
||||
|
||||
local element = require("graphics.element")
|
||||
|
||||
@@ -8,8 +8,9 @@ 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
|
||||
|
||||
-- new waiting animation element
|
||||
---@param args waiting_args
|
||||
@@ -102,7 +103,7 @@ local function waiting(args)
|
||||
|
||||
e.start_anim()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return waiting
|
||||
|
||||
@@ -8,7 +8,8 @@ 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
|
||||
---@param args colormap_args
|
||||
@@ -27,7 +28,7 @@ local function colormap(args)
|
||||
e.window.setCursorPos(1, 1)
|
||||
e.window.blit(spaces, bkg, bkg)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return colormap
|
||||
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Hazard-bordered Button Graphics Element
|
||||
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
@@ -14,8 +14,9 @@ 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
|
||||
|
||||
-- new hazard button
|
||||
---@param args hazard_button_args
|
||||
@@ -198,7 +199,7 @@ local function hazard_button(args)
|
||||
-- initial draw of border
|
||||
draw_border(args.accent)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return hazard_button
|
||||
|
||||
@@ -20,9 +20,10 @@ 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
|
||||
|
||||
-- new multi button (latch selection, exclusively one button at a time)
|
||||
---@param args multi_button_args
|
||||
@@ -130,7 +131,7 @@ local function multi_button(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return multi_button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Button Graphics Element
|
||||
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local tcd = require("scada-common.tcd")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local element = require("graphics.element")
|
||||
@@ -16,9 +16,10 @@ 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
|
||||
|
||||
-- new push button
|
||||
---@param args push_button_args
|
||||
@@ -120,7 +121,7 @@ local function push_button(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return push_button
|
||||
|
||||
@@ -13,8 +13,9 @@ 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
|
||||
|
||||
-- new radio button list (latch selection, exclusively one button at a time)
|
||||
---@param args radio_button_args
|
||||
@@ -103,7 +104,7 @@ local function radio_button(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return radio_button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Sidebar Graphics Element
|
||||
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local tcd = require("scada-common.tcd")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local element = require("graphics.element")
|
||||
@@ -17,9 +17,10 @@ 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
|
||||
|
||||
-- new sidebar tab selector
|
||||
---@param args sidebar_args
|
||||
@@ -115,7 +116,7 @@ local function sidebar(args)
|
||||
-- initial draw
|
||||
draw(false)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return sidebar
|
||||
|
||||
@@ -16,8 +16,9 @@ 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
|
||||
|
||||
-- new spinbox control (minimum value is 0)
|
||||
---@param args spinbox_args
|
||||
@@ -188,7 +189,7 @@ local function spinbox(args)
|
||||
e.value = 0
|
||||
set_digits()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return spinbox
|
||||
|
||||
@@ -12,9 +12,10 @@ 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
|
||||
|
||||
-- new switch button (latch high/low)
|
||||
---@param args switch_button_args
|
||||
@@ -86,7 +87,7 @@ local function switch_button(args)
|
||||
draw_state()
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return switch_button
|
||||
|
||||
@@ -18,9 +18,10 @@ 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
|
||||
|
||||
-- new tab selector
|
||||
---@param args tabbar_args
|
||||
@@ -124,7 +125,7 @@ local function tabbar(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return tabbar
|
||||
|
||||
@@ -4,19 +4,22 @@ local element = require("graphics.element")
|
||||
|
||||
---@class displaybox_args
|
||||
---@field window table
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field width? integer parent width if omitted
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new root display box
|
||||
---@nodiscard
|
||||
---@param args displaybox_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function displaybox(args)
|
||||
-- create new graphics element base object
|
||||
return element.new(args).get()
|
||||
return element.new(args).complete()
|
||||
end
|
||||
|
||||
return displaybox
|
||||
|
||||
@@ -6,11 +6,12 @@ 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new div element
|
||||
---@nodiscard
|
||||
@@ -18,7 +19,7 @@ local element = require("graphics.element")
|
||||
---@return graphics_element element, element_id id
|
||||
local function div(args)
|
||||
-- create new graphics element base object
|
||||
return element.new(args).get()
|
||||
return element.new(args).complete()
|
||||
end
|
||||
|
||||
return div
|
||||
|
||||
@@ -16,8 +16,9 @@ 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
|
||||
|
||||
-- new alarm indicator light
|
||||
---@nodiscard
|
||||
@@ -108,7 +109,7 @@ local function alarm_indicator_light(args)
|
||||
e.on_update(1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return alarm_indicator_light
|
||||
|
||||
@@ -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
|
||||
@@ -163,7 +163,7 @@ local function core_map(args)
|
||||
-- initial draw
|
||||
e.on_update(0)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return core_map
|
||||
|
||||
@@ -14,9 +14,10 @@ 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
|
||||
|
||||
-- new data indicator
|
||||
---@nodiscard
|
||||
@@ -43,8 +44,9 @@ local function data(args)
|
||||
e.window.setCursorPos(1, 1)
|
||||
e.window.write(args.label)
|
||||
|
||||
local label_len = string.len(args.label)
|
||||
local data_start = 1
|
||||
local value_color = e.fg_bg.fgd
|
||||
local label_len = string.len(args.label)
|
||||
local data_start = 1
|
||||
local clear_width = args.width
|
||||
|
||||
if label_len > 0 then
|
||||
@@ -64,7 +66,7 @@ local function data(args)
|
||||
-- write data
|
||||
local data_str = util.sprintf(args.format, value)
|
||||
e.window.setCursorPos(data_start, 1)
|
||||
e.window.setTextColor(e.fg_bg.fgd)
|
||||
e.window.setTextColor(value_color)
|
||||
if args.commas then
|
||||
e.window.write(util.comma_format(data_str))
|
||||
else
|
||||
@@ -84,10 +86,17 @@ local function data(args)
|
||||
---@param val any new value
|
||||
function e.set_value(val) e.on_update(val) end
|
||||
|
||||
-- change the foreground color of the value, or all text if no label/unit colors provided
|
||||
---@param c color
|
||||
function e.recolor(c)
|
||||
value_color = c
|
||||
e.on_update(e.value)
|
||||
end
|
||||
|
||||
-- initial value draw
|
||||
e.on_update(args.value)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return data
|
||||
|
||||
@@ -10,11 +10,12 @@ 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new horizontal bar
|
||||
---@nodiscard
|
||||
@@ -119,7 +120,7 @@ local function hbar(args)
|
||||
-- initialize to 0
|
||||
e.on_update(0)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return hbar
|
||||
|
||||
@@ -16,8 +16,9 @@ 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
|
||||
|
||||
-- new icon indicator
|
||||
---@nodiscard
|
||||
@@ -68,7 +69,7 @@ local function icon(args)
|
||||
-- initial icon draw
|
||||
e.on_update(args.value or 1)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return icon
|
||||
|
||||
@@ -14,8 +14,9 @@ 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
|
||||
|
||||
-- new indicator LED
|
||||
---@nodiscard
|
||||
@@ -94,7 +95,7 @@ local function indicator_led(args)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_led
|
||||
|
||||
@@ -16,8 +16,9 @@ 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
|
||||
|
||||
-- new dual LED indicator light
|
||||
---@nodiscard
|
||||
@@ -108,7 +109,7 @@ local function indicator_led_pair(args)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_led_pair
|
||||
|
||||
@@ -9,8 +9,9 @@ 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
|
||||
|
||||
-- new RGB LED indicator light
|
||||
---@nodiscard
|
||||
@@ -53,7 +54,7 @@ local function indicator_led_rgb(args)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_led_rgb
|
||||
|
||||
@@ -14,8 +14,9 @@ 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
|
||||
|
||||
-- new indicator light
|
||||
---@nodiscard
|
||||
@@ -92,7 +93,7 @@ local function indicator_light(args)
|
||||
e.window.setCursorPos(3, 1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_light
|
||||
|
||||
@@ -13,9 +13,10 @@ 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
|
||||
|
||||
-- new power indicator
|
||||
---@nodiscard
|
||||
@@ -79,7 +80,7 @@ local function power(args)
|
||||
-- initial value draw
|
||||
e.on_update(args.value)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return power
|
||||
|
||||
@@ -14,9 +14,10 @@ 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
|
||||
|
||||
-- new radiation indicator
|
||||
---@nodiscard
|
||||
@@ -84,7 +85,7 @@ local function rad(args)
|
||||
-- initial value draw
|
||||
e.on_update(types.new_zero_radiation_reading())
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return rad
|
||||
|
||||
@@ -15,9 +15,10 @@ 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
|
||||
|
||||
-- new state indicator
|
||||
---@nodiscard
|
||||
@@ -74,7 +75,7 @@ local function state_indicator(args)
|
||||
-- initial draw
|
||||
e.on_update(args.value or 1)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return state_indicator
|
||||
|
||||
@@ -16,8 +16,9 @@ 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
|
||||
|
||||
-- new tri-state indicator light
|
||||
---@nodiscard
|
||||
@@ -105,7 +106,7 @@ local function tristate_indicator_light(args)
|
||||
e.on_update(1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return tristate_indicator_light
|
||||
|
||||
@@ -8,11 +8,12 @@ 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new vertical bar
|
||||
---@nodiscard
|
||||
@@ -99,7 +100,7 @@ local function vbar(args)
|
||||
---@param val number 0.0 to 1.0
|
||||
function e.set_value(val) e.on_update(val) end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return vbar
|
||||
|
||||
283
graphics/elements/listbox.lua
Normal file
283
graphics/elements/listbox.lua
Normal file
@@ -0,0 +1,283 @@
|
||||
-- Scroll-able List Box Display 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 listbox_args
|
||||
---@field scroll_height integer height of internal scrolling container (must fit all elements vertically tiled)
|
||||
---@field item_pad? integer spacing (lines) between items in the list (default 0)
|
||||
---@field nav_fg_bg? cpair foreground/background colors for scroll arrows and bar area
|
||||
---@field nav_active? cpair active colors for bar held down or arrow held down
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
---@class listbox_item
|
||||
---@field id string|integer element ID
|
||||
---@field e graphics_element element
|
||||
---@field y integer y position
|
||||
---@field h integer element height
|
||||
|
||||
-- new listbox element
|
||||
---@nodiscard
|
||||
---@param args listbox_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function listbox(args)
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
-- create content window for child elements
|
||||
local scroll_frame = window.create(e.window, 1, 1, e.frame.w - 1, args.scroll_height, false)
|
||||
e.content_window = scroll_frame
|
||||
|
||||
-- item list and scroll management
|
||||
local list = {}
|
||||
local item_pad = args.item_pad or 0
|
||||
local scroll_offset = 0
|
||||
local content_height = 0
|
||||
local max_down_scroll = 0
|
||||
-- bar control/tracking variables
|
||||
local max_bar_height = e.frame.h - 2
|
||||
local bar_height = 0 -- full height of bar
|
||||
local bar_bounds = { 0, 0 } -- top and bottom of bar
|
||||
local bar_is_scaled = false -- if the scrollbar doesn't have a 1:1 ratio with lines
|
||||
local holding_bar = false -- bar is being held by mouse
|
||||
local bar_grip_pos = 0 -- where the bar was gripped by mouse down
|
||||
local mouse_last_y = 0 -- last reported y coordinate of drag
|
||||
|
||||
-- draw scroll bar arrows, optionally showing one of them as pressed
|
||||
---@param pressed_arrow? 1|0|-1 arrow to show as pressed (1 = scroll up, 0 = neither, -1 = scroll down)
|
||||
local function draw_arrows(pressed_arrow)
|
||||
local nav_fg_bg = args.nav_fg_bg or e.fg_bg
|
||||
local active_fg_bg = args.nav_active or nav_fg_bg
|
||||
|
||||
-- draw up/down arrows
|
||||
if pressed_arrow == 1 then
|
||||
e.window.setTextColor(active_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(active_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, 1)
|
||||
e.window.write("\x1e")
|
||||
e.window.setTextColor(nav_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(nav_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, e.frame.h)
|
||||
e.window.write("\x1f")
|
||||
elseif pressed_arrow == -1 then
|
||||
e.window.setTextColor(nav_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(nav_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, 1)
|
||||
e.window.write("\x1e")
|
||||
e.window.setTextColor(active_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(active_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, e.frame.h)
|
||||
e.window.write("\x1f")
|
||||
else
|
||||
e.window.setTextColor(nav_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(nav_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, 1)
|
||||
e.window.write("\x1e")
|
||||
e.window.setCursorPos(e.frame.w, e.frame.h)
|
||||
e.window.write("\x1f")
|
||||
end
|
||||
|
||||
e.window.setTextColor(e.fg_bg.fgd)
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
end
|
||||
|
||||
-- render the scroll bar and re-cacluate height & bounds
|
||||
local function draw_bar()
|
||||
local offset = 2 + math.abs(scroll_offset)
|
||||
|
||||
bar_height = math.min(max_bar_height + max_down_scroll, max_bar_height)
|
||||
|
||||
if bar_height < 1 then
|
||||
bar_is_scaled = true
|
||||
-- can't do a 1:1 ratio
|
||||
-- use minimum size bar with scaled offset
|
||||
local scroll_progress = scroll_offset / max_down_scroll
|
||||
offset = 2 + math.floor(scroll_progress * (max_bar_height - 1))
|
||||
bar_height = 1
|
||||
else
|
||||
bar_is_scaled = false
|
||||
end
|
||||
|
||||
bar_bounds = { offset, (bar_height + offset) - 1 }
|
||||
|
||||
for i = 2, e.frame.h - 1 do
|
||||
if (i >= offset and i < (bar_height + offset)) and (bar_height ~= max_bar_height) then
|
||||
if args.nav_fg_bg ~= nil then
|
||||
e.window.setBackgroundColor(args.nav_fg_bg.fgd)
|
||||
else
|
||||
e.window.setBackgroundColor(e.fg_bg.fgd)
|
||||
end
|
||||
else
|
||||
if args.nav_fg_bg ~= nil then
|
||||
e.window.setBackgroundColor(args.nav_fg_bg.bkg)
|
||||
else
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
end
|
||||
end
|
||||
|
||||
e.window.setCursorPos(e.frame.w, i)
|
||||
e.window.write(" ")
|
||||
end
|
||||
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
end
|
||||
|
||||
-- update item y positions and move elements
|
||||
local function update_positions()
|
||||
local next_y = 1
|
||||
|
||||
scroll_frame.setVisible(false)
|
||||
scroll_frame.setBackgroundColor(e.fg_bg.bkg)
|
||||
scroll_frame.setTextColor(e.fg_bg.fgd)
|
||||
scroll_frame.clear()
|
||||
|
||||
for i = 1, #list do
|
||||
local item = list[i] ---@type listbox_item
|
||||
item.y = next_y
|
||||
next_y = next_y + item.h + item_pad
|
||||
item.e.reposition(1, item.y)
|
||||
item.e.show()
|
||||
end
|
||||
|
||||
content_height = next_y
|
||||
max_down_scroll = math.min(-1 * (content_height - (e.frame.h + 1 + item_pad)), 0)
|
||||
if scroll_offset < max_down_scroll then scroll_offset = max_down_scroll end
|
||||
|
||||
scroll_frame.reposition(1, 1 + scroll_offset)
|
||||
scroll_frame.setVisible(true)
|
||||
|
||||
draw_bar()
|
||||
end
|
||||
|
||||
-- determine where to scroll to based on a scrollbar being dragged without a 1:1 relationship
|
||||
---@param direction -1|1 negative 1 to scroll up by one, positive 1 to scroll down by one
|
||||
local function scaled_bar_scroll(direction)
|
||||
local scroll_progress = scroll_offset / max_down_scroll
|
||||
local bar_position = math.floor(scroll_progress * (max_bar_height - 1))
|
||||
|
||||
-- check what moving the scroll bar up or down would mean for the scroll progress
|
||||
scroll_progress = (bar_position + direction) / (max_bar_height - 1)
|
||||
|
||||
return math.max(math.floor(scroll_progress * max_down_scroll), max_down_scroll)
|
||||
end
|
||||
|
||||
-- scroll down the list
|
||||
local function scroll_down(scaled)
|
||||
if scroll_offset > max_down_scroll then
|
||||
if scaled then
|
||||
scroll_offset = scaled_bar_scroll(1)
|
||||
else
|
||||
scroll_offset = scroll_offset - 1
|
||||
end
|
||||
|
||||
update_positions()
|
||||
end
|
||||
end
|
||||
|
||||
-- scroll up the list
|
||||
local function scroll_up(scaled)
|
||||
if scroll_offset < 0 then
|
||||
if scaled then
|
||||
scroll_offset = scaled_bar_scroll(-1)
|
||||
else
|
||||
scroll_offset = scroll_offset + 1
|
||||
end
|
||||
|
||||
update_positions()
|
||||
end
|
||||
end
|
||||
|
||||
-- handle a child element having been added to the list
|
||||
---@param id element_id element identifier
|
||||
---@param child graphics_element child element
|
||||
function e.on_added(id, child)
|
||||
table.insert(list, { id = id, e = child, y = 0, h = child.get_height() })
|
||||
update_positions()
|
||||
end
|
||||
|
||||
-- handle a child element having been removed from the list
|
||||
---@param id element_id element identifier
|
||||
function e.on_removed(id)
|
||||
for idx, elem in ipairs(list) do
|
||||
if elem.id == id then
|
||||
table.remove(list, idx)
|
||||
update_positions()
|
||||
return
|
||||
end
|
||||
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
|
||||
if event.current.x == e.frame.w then
|
||||
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
||||
draw_arrows(1)
|
||||
scroll_up()
|
||||
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
||||
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
||||
draw_arrows(-1)
|
||||
scroll_down()
|
||||
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
||||
end
|
||||
end
|
||||
elseif event.type == CLICK_TYPE.DOWN then
|
||||
if event.current.x == e.frame.w then
|
||||
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
||||
draw_arrows(1)
|
||||
scroll_up()
|
||||
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
||||
draw_arrows(-1)
|
||||
scroll_down()
|
||||
else
|
||||
-- clicked on bar
|
||||
holding_bar = true
|
||||
bar_grip_pos = event.current.y - bar_bounds[1]
|
||||
mouse_last_y = event.current.y
|
||||
end
|
||||
end
|
||||
elseif event.type == CLICK_TYPE.UP then
|
||||
holding_bar = false
|
||||
draw_arrows(0)
|
||||
elseif event.type == CLICK_TYPE.DRAG then
|
||||
if holding_bar then
|
||||
-- if mouse is within vertical frame, including the grip point
|
||||
if event.current.y > (1 + bar_grip_pos) and event.current.y <= ((e.frame.h - bar_height) + bar_grip_pos) then
|
||||
if event.current.y < mouse_last_y then
|
||||
scroll_up(bar_is_scaled)
|
||||
elseif event.current.y > mouse_last_y then
|
||||
scroll_down(bar_is_scaled)
|
||||
end
|
||||
|
||||
mouse_last_y = event.current.y
|
||||
end
|
||||
end
|
||||
elseif event.type == CLICK_TYPE.SCROLL_DOWN then
|
||||
scroll_down()
|
||||
elseif event.type == CLICK_TYPE.SCROLL_UP then
|
||||
scroll_up()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
draw_arrows(0)
|
||||
draw_bar()
|
||||
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return listbox
|
||||
@@ -7,11 +7,12 @@ 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new multipane element
|
||||
---@nodiscard
|
||||
@@ -36,7 +37,7 @@ local function multipane(args)
|
||||
|
||||
e.set_value(1)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return multipane
|
||||
|
||||
@@ -11,7 +11,8 @@ 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 pipe network
|
||||
---@param args pipenet_args
|
||||
@@ -141,7 +142,7 @@ local function pipenet(args)
|
||||
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return pipenet
|
||||
|
||||
@@ -11,11 +11,12 @@ 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new rectangle
|
||||
---@param args rectangle_args
|
||||
@@ -30,27 +31,35 @@ local function rectangle(args)
|
||||
end
|
||||
|
||||
-- offset children
|
||||
local offset_x = 0
|
||||
local offset_y = 0
|
||||
if args.border ~= nil then
|
||||
args.offset_x = args.border.width
|
||||
args.offset_y = args.border.width
|
||||
offset_x = args.border.width
|
||||
offset_y = args.border.width
|
||||
|
||||
-- slightly different y offset if the border is set to even
|
||||
if args.border.even then
|
||||
local width_x2 = (2 * args.border.width)
|
||||
args.offset_y = math.floor(width_x2 / 3) + util.trinary(width_x2 % 3 > 0, 1, 0)
|
||||
offset_y = math.floor(width_x2 / 3) + util.trinary(width_x2 % 3 > 0, 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
local e = element.new(args, offset_x, offset_y)
|
||||
|
||||
-- create content window for child elements
|
||||
e.content_window = window.create(e.window, 1 + offset_x, 1 + offset_y, e.frame.w - (2 * offset_x), e.frame.h - (2 * offset_y))
|
||||
e.content_window.setBackgroundColor(e.fg_bg.bkg)
|
||||
e.content_window.setTextColor(e.fg_bg.fgd)
|
||||
e.content_window.clear()
|
||||
|
||||
-- draw bordered box if requested
|
||||
-- element constructor will have drawn basic colored rectangle regardless
|
||||
if args.border ~= nil then
|
||||
e.window.setCursorPos(1, 1)
|
||||
|
||||
local border_width = args.offset_x
|
||||
local border_height = args.offset_y
|
||||
local border_width = offset_x
|
||||
local border_height = offset_y
|
||||
local border_blit = colors.toBlit(args.border.color)
|
||||
local width_x2 = border_width * 2
|
||||
local inner_width = e.frame.w - width_x2
|
||||
@@ -177,7 +186,7 @@ local function rectangle(args)
|
||||
end
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return rectangle
|
||||
|
||||
@@ -13,11 +13,12 @@ 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new text box
|
||||
---@param args textbox_args
|
||||
@@ -64,7 +65,7 @@ local function textbox(args)
|
||||
display_text(val)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return textbox
|
||||
|
||||
@@ -11,11 +11,12 @@ 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new tiling box
|
||||
---@param args tiling_args
|
||||
@@ -81,7 +82,7 @@ local function tiling(args)
|
||||
if inner_width % 2 == 0 then alternator = not alternator end
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return tiling
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- Indicator Light Flasher
|
||||
--
|
||||
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local tcd = require("scada-common.tcd")
|
||||
|
||||
local flasher = {}
|
||||
|
||||
@@ -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
|
||||
|
||||
20
imgen.py
20
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")
|
||||
@@ -49,6 +49,8 @@ def make_manifest(size):
|
||||
"installer" : get_version("./ccmsi.lua"),
|
||||
"bootloader" : get_version("./startup.lua"),
|
||||
"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 +71,11 @@ def make_manifest(size):
|
||||
"pocket" : list_files("./pocket"),
|
||||
},
|
||||
"depends" : {
|
||||
"reactor-plc" : [ "system", "common", "graphics" ],
|
||||
"rtu" : [ "system", "common", "graphics" ],
|
||||
"supervisor" : [ "system", "common" ],
|
||||
"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
|
||||
@@ -111,7 +113,7 @@ f.close()
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "shields":
|
||||
# write all the JSON files for shields.io
|
||||
for key, version in final_manifest["versions"].items():
|
||||
f = open("./shields/" + key + ".json", "w")
|
||||
f = open("./deploy/" + key + ".json", "w")
|
||||
|
||||
if version.find("alpha") >= 0:
|
||||
color = "yellow"
|
||||
|
||||
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
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
local config = {}
|
||||
|
||||
-- port of the SCADA supervisor
|
||||
config.SCADA_SV_PORT = 16100
|
||||
-- port for SCADA coordinator API access
|
||||
config.SCADA_API_PORT = 16200
|
||||
-- port to listen to incoming packets FROM servers
|
||||
config.LISTEN_PORT = 16201
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- coordinator comms channel
|
||||
config.CRD_CHANNEL = 16243
|
||||
-- pocket comms channel
|
||||
config.PKT_CHANNEL = 16244
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
config.COMMS_TIMEOUT = 5
|
||||
-- facility authentication key (do NOT use one of your passwords)
|
||||
-- this enables verifying that messages are authentic
|
||||
-- all devices on the same network must use the same key
|
||||
-- config.AUTH_KEY = "SCADAfacility123"
|
||||
|
||||
-- log path
|
||||
config.LOG_PATH = "/log.txt"
|
||||
|
||||
@@ -17,23 +17,25 @@ local pocket = {}
|
||||
-- pocket coordinator + supervisor communications
|
||||
---@nodiscard
|
||||
---@param version string pocket version
|
||||
---@param modem table modem device
|
||||
---@param local_port integer local pocket port
|
||||
---@param sv_port integer port of supervisor
|
||||
---@param api_port integer port of coordinator API
|
||||
---@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, local_port, sv_port, api_port, 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,
|
||||
addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
r_seq_num = nil, ---@type nil|integer
|
||||
last_est_ack = ESTABLISH_ACK.ALLOW
|
||||
},
|
||||
api = {
|
||||
linked = false,
|
||||
addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
r_seq_num = nil, ---@type nil|integer
|
||||
last_est_ack = ESTABLISH_ACK.ALLOW
|
||||
@@ -45,13 +47,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(local_port)
|
||||
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
|
||||
@@ -61,9 +59,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
local pkt = comms.mgmt_packet()
|
||||
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
s_pkt.make(self.sv.addr, self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(sv_port, local_port, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, pkt_channel, s_pkt)
|
||||
self.sv.seq_num = self.sv.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -75,9 +73,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
local pkt = comms.mgmt_packet()
|
||||
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(api_port, local_port, s_pkt.raw_sendable())
|
||||
nic.transmit(crd_channel, pkt_channel, s_pkt)
|
||||
self.api.seq_num = self.api.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -89,9 +87,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
-- local pkt = comms.capi_packet()
|
||||
|
||||
-- pkt.make(msg_type, msg)
|
||||
-- s_pkt.make(self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable())
|
||||
-- s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable())
|
||||
|
||||
-- modem.transmit(api_port, local_port, s_pkt.raw_sendable())
|
||||
-- nic.transmit(crd_channel, pkt_channel, s_pkt)
|
||||
-- self.api.seq_num = self.api.seq_num + 1
|
||||
-- end
|
||||
|
||||
@@ -122,17 +120,12 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
---@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()
|
||||
self.sv.linked = false
|
||||
self.sv.r_seq_num = nil
|
||||
self.sv.addr = comms.BROADCAST
|
||||
_send_sv(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
end
|
||||
|
||||
@@ -140,6 +133,8 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
function public.close_api()
|
||||
api_watchdog.cancel()
|
||||
self.api.linked = false
|
||||
self.api.r_seq_num = nil
|
||||
self.api.addr = comms.BROADCAST
|
||||
_send_crd(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
end
|
||||
|
||||
@@ -183,13 +178,10 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
---@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()
|
||||
@@ -214,18 +206,23 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
---@param packet mgmt_frame|capi_frame|nil
|
||||
function public.handle_packet(packet)
|
||||
if packet ~= nil then
|
||||
local l_port = packet.scada_frame.local_port()
|
||||
local r_port = packet.scada_frame.remote_port()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local r_chan = packet.scada_frame.remote_channel()
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
|
||||
if l_port ~= local_port then
|
||||
log.debug("received packet on unconfigured channel " .. l_port, true)
|
||||
elseif r_port == api_port then
|
||||
if l_chan ~= pkt_channel then
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
elseif r_chan == crd_channel then
|
||||
-- check sequence number
|
||||
if self.api.r_seq_num == nil then
|
||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif self.connected and ((self.api.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
log.warning("sequence out-of-order (API): last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif self.api.linked and (src_addr ~= self.api.addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
||||
@@ -247,6 +244,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
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)
|
||||
@@ -294,6 +292,8 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
-- handle session close
|
||||
api_watchdog.cancel()
|
||||
self.api.linked = false
|
||||
self.api.r_seq_num = nil
|
||||
self.api.addr = comms.BROADCAST
|
||||
log.info("coordinator server connection closed by remote host")
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator")
|
||||
@@ -304,12 +304,16 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
else
|
||||
log.debug("illegal packet type " .. protocol .. " from coordinator", true)
|
||||
end
|
||||
elseif r_port == sv_port then
|
||||
elseif r_chan == svr_channel then
|
||||
-- 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
|
||||
log.warning("sequence out-of-order: last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
log.warning("sequence out-of-order (SVR): last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif self.sv.linked and (src_addr ~= self.sv.addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.sv.r_seq_num = packet.scada_frame.seq_num()
|
||||
@@ -330,6 +334,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
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)
|
||||
@@ -377,6 +382,8 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
-- handle session close
|
||||
sv_watchdog.cancel()
|
||||
self.sv.linked = false
|
||||
self.sv.r_seq_num = nil
|
||||
self.sv.addr = comms.BROADCAST
|
||||
log.info("supervisor server connection closed by remote host")
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
|
||||
@@ -388,7 +395,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
log.debug("illegal packet type " .. protocol .. " from supervisor", true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet from unconfigured channel " .. r_port, true)
|
||||
log.debug("received packet from unconfigured channel " .. r_chan, true)
|
||||
end
|
||||
end
|
||||
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 tcallbackdsp = require("scada-common.tcallbackdsp")
|
||||
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 coreio = require("pocket.coreio")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
|
||||
local POCKET_VERSION = "alpha-v0.3.2"
|
||||
local POCKET_VERSION = "alpha-v0.5.2"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -28,9 +29,9 @@ local println_ts = util.println_ts
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_port(config.SCADA_SV_PORT)
|
||||
cfv.assert_port(config.SCADA_API_PORT)
|
||||
cfv.assert_port(config.LISTEN_PORT)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.CRD_CHANNEL)
|
||||
cfv.assert_channel(config.PKT_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.COMMS_TIMEOUT)
|
||||
cfv.assert_min(config.COMMS_TIMEOUT, 2)
|
||||
@@ -67,6 +68,11 @@ local function main()
|
||||
-- setup communications & clocks
|
||||
----------------------------------------
|
||||
|
||||
-- message authentication init
|
||||
if type(config.AUTH_KEY) == "string" then
|
||||
network.init_mac(config.AUTH_KEY)
|
||||
end
|
||||
|
||||
coreio.report_link_state(coreio.LINK_STATE.UNLINKED)
|
||||
|
||||
-- get the communications modem
|
||||
@@ -88,9 +94,10 @@ local function main()
|
||||
|
||||
log.debug("startup> conn watchdogs created")
|
||||
|
||||
-- start comms, open all channels
|
||||
local pocket_comms = pocket.comms(POCKET_VERSION, modem, config.LISTEN_PORT, config.SCADA_SV_PORT,
|
||||
config.SCADA_API_PORT, config.TRUSTED_RANGE, conn_wd.sv, conn_wd.api)
|
||||
-- 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")
|
||||
|
||||
-- base loop clock (2Hz, 10 ticks)
|
||||
@@ -105,7 +112,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()
|
||||
@@ -120,54 +127,54 @@ local function main()
|
||||
conn_wd.sv.feed()
|
||||
conn_wd.api.feed()
|
||||
log.debug("startup> conn watchdog started")
|
||||
end
|
||||
|
||||
-- main event loop
|
||||
while ui_ok do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
-- main event loop
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
|
||||
-- handle event
|
||||
if event == "timer" then
|
||||
if loop_clock.is_clock(param1) then
|
||||
-- main loop tick
|
||||
-- handle event
|
||||
if event == "timer" then
|
||||
if loop_clock.is_clock(param1) then
|
||||
-- main loop tick
|
||||
|
||||
-- relink if necessary
|
||||
pocket_comms.link_update()
|
||||
-- relink if necessary
|
||||
pocket_comms.link_update()
|
||||
|
||||
loop_clock.start()
|
||||
elseif conn_wd.sv.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
log.info("supervisor server timeout")
|
||||
pocket_comms.close_sv()
|
||||
elseif conn_wd.api.is_timer(param1) then
|
||||
-- coordinator watchdog timeout
|
||||
log.info("coordinator api server timeout")
|
||||
pocket_comms.close_api()
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
-- notify timer callback dispatcher
|
||||
tcallbackdsp.handle(param1)
|
||||
loop_clock.start()
|
||||
elseif conn_wd.sv.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
log.info("supervisor server timeout")
|
||||
pocket_comms.close_sv()
|
||||
elseif conn_wd.api.is_timer(param1) then
|
||||
-- coordinator watchdog timeout
|
||||
log.info("coordinator api server timeout")
|
||||
pocket_comms.close_api()
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
-- notify timer callback dispatcher
|
||||
tcd.handle(param1)
|
||||
end
|
||||
elseif event == "modem_message" then
|
||||
-- got a packet
|
||||
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
pocket_comms.handle_packet(packet)
|
||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
|
||||
-- handle a monitor touch event
|
||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||
end
|
||||
|
||||
-- check for termination request
|
||||
if event == "terminate" or ppm.should_terminate() then
|
||||
log.info("terminate requested, closing server connections...")
|
||||
pocket_comms.close()
|
||||
log.info("connections closed")
|
||||
break
|
||||
end
|
||||
elseif event == "modem_message" then
|
||||
-- got a packet
|
||||
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
pocket_comms.handle_packet(packet)
|
||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
|
||||
-- handle a monitor touch event
|
||||
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
|
||||
end
|
||||
|
||||
-- check for termination request
|
||||
if event == "terminate" or ppm.should_terminate() then
|
||||
log.info("terminate requested, closing server connections...")
|
||||
pocket_comms.close()
|
||||
log.info("connections closed")
|
||||
break
|
||||
end
|
||||
renderer.close_ui()
|
||||
end
|
||||
|
||||
renderer.close_ui()
|
||||
|
||||
println_ts("exited")
|
||||
log.info("exited")
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ local function init(parent, y, is_api)
|
||||
-- bounding box div
|
||||
local box = Div{parent=root,x=1,y=y,height=5}
|
||||
|
||||
local waiting_x = math.floor(parent.width() / 2) - 1
|
||||
local waiting_x = math.floor(parent.get_width() / 2) - 1
|
||||
|
||||
if is_api then
|
||||
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.blue,style.root.bkg)}
|
||||
|
||||
@@ -8,11 +8,11 @@ local style = require("pocket.ui.style")
|
||||
|
||||
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
||||
|
||||
local home_page = require("pocket.ui.components.home_page")
|
||||
local unit_page = require("pocket.ui.components.unit_page")
|
||||
local reactor_page = require("pocket.ui.components.reactor_page")
|
||||
local boiler_page = require("pocket.ui.components.boiler_page")
|
||||
local turbine_page = require("pocket.ui.components.turbine_page")
|
||||
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 turbine_page = require("pocket.ui.pages.turbine_page")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
|
||||
@@ -9,14 +9,18 @@ config.REACTOR_ID = 1
|
||||
-- when emergency coolant is needed due to low coolant
|
||||
-- config.EMERGENCY_COOL = { side = "right", color = nil }
|
||||
|
||||
-- port to send packets TO server
|
||||
config.SERVER_PORT = 16000
|
||||
-- port to listen to incoming packets FROM server
|
||||
config.LISTEN_PORT = 14001
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- PLC comms channel
|
||||
config.PLC_CHANNEL = 16241
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
config.COMMS_TIMEOUT = 5
|
||||
-- facility authentication key (do NOT use one of your passwords)
|
||||
-- this enables verifying that messages are authentic
|
||||
-- all devices on the same network must use the same key
|
||||
-- config.AUTH_KEY = "SCADAfacility123"
|
||||
|
||||
-- log path
|
||||
config.LOG_PATH = "/log.txt"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
--
|
||||
-- Main SCADA Coordinator GUI
|
||||
-- Reactor PLC Front Panel GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("reactor-plc.config")
|
||||
@@ -27,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}
|
||||
@@ -49,7 +50,7 @@ local function init(panel)
|
||||
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
|
||||
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(5)
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
system.line_break()
|
||||
|
||||
reactor.register(databus.ps, "reactor_dev_state", reactor.update)
|
||||
@@ -69,6 +70,10 @@ local function init(panel)
|
||||
rt_cmrx.register(databus.ps, "routine__comms_rx", rt_cmrx.update)
|
||||
rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=5,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
|
||||
--
|
||||
-- status & controls
|
||||
--
|
||||
|
||||
@@ -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,15 +445,16 @@ end
|
||||
---@nodiscard
|
||||
---@param id integer reactor ID
|
||||
---@param version string PLC version
|
||||
---@param modem table modem device
|
||||
---@param local_port integer local listening port
|
||||
---@param server_port integer remote server port
|
||||
---@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, local_port, server_port, 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,
|
||||
r_seq_num = nil,
|
||||
scrammed = false,
|
||||
@@ -469,13 +470,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(local_port)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
-- configure network channels
|
||||
nic.closeAll()
|
||||
nic.open(plc_channel)
|
||||
|
||||
-- send an RPLC packet
|
||||
---@param msg_type RPLC_TYPE
|
||||
@@ -485,9 +482,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
local r_pkt = comms.rplc_packet()
|
||||
|
||||
r_pkt.make(id, msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, plc_channel, s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -499,9 +496,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, plc_channel, s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -638,7 +635,7 @@ function plc.comms(id, version, modem, local_port, server_port, 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
|
||||
@@ -649,13 +646,6 @@ function plc.comms(id, version, modem, local_port, server_port, 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)
|
||||
@@ -667,9 +657,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
|
||||
-- unlink from the server
|
||||
function public.unlink()
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.linked = false
|
||||
self.r_seq_num = nil
|
||||
self.status_cache = nil
|
||||
databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
end
|
||||
|
||||
-- close the connection to the server
|
||||
@@ -731,7 +723,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
end
|
||||
end
|
||||
|
||||
-- parse an RPLC packet
|
||||
-- parse a packet
|
||||
---@nodiscard
|
||||
---@param side string
|
||||
---@param sender integer
|
||||
@@ -740,13 +732,10 @@ function plc.comms(id, version, modem, local_port, server_port, 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()
|
||||
@@ -760,14 +749,14 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
pkt = mgmt_pkt.get()
|
||||
end
|
||||
else
|
||||
log.debug("illegal packet type " .. s_pkt.protocol(), true)
|
||||
log.debug("unsupported packet type " .. s_pkt.protocol(), true)
|
||||
end
|
||||
end
|
||||
|
||||
return pkt
|
||||
end
|
||||
|
||||
-- handle an RPLC packet
|
||||
-- handle RPLC and MGMT packets
|
||||
---@param packet rplc_frame|mgmt_frame packet frame
|
||||
---@param plc_state plc_state PLC state
|
||||
---@param setpoints setpoints setpoint control table
|
||||
@@ -775,16 +764,22 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println_ts(message) if not plc_state.fp_ok then util.println_ts(message) end end
|
||||
|
||||
local l_port = packet.scada_frame.local_port()
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
|
||||
-- handle packets now that we have prints setup
|
||||
if l_port == local_port then
|
||||
if l_chan == plc_channel then
|
||||
-- check sequence number
|
||||
if self.r_seq_num == nil then
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif self.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif self.linked and (src_addr ~= self.sv_addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
end
|
||||
@@ -792,11 +787,10 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
-- feed the watchdog first so it doesn't uhh...eat our packets :)
|
||||
conn_watchdog.feed()
|
||||
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
-- handle packet
|
||||
if protocol == PROTOCOL.RPLC then
|
||||
---@cast packet rplc_frame
|
||||
-- if linked, only accept packets from configured supervisor
|
||||
if self.linked then
|
||||
if packet.type == RPLC_TYPE.STATUS then
|
||||
-- request of full status, clear cache first
|
||||
@@ -828,7 +822,7 @@ function plc.comms(id, version, modem, local_port, server_port, 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)
|
||||
@@ -933,44 +927,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@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")
|
||||
elseif 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
|
||||
|
||||
self.linked = est_ack == ESTABLISH_ACK.ALLOW
|
||||
|
||||
-- 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]
|
||||
@@ -980,7 +939,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
log.warning("PLC KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug("RPLC RTT = " .. trip_time .. "ms")
|
||||
-- log.debug("PLC RTT = " .. trip_time .. "ms")
|
||||
|
||||
_send_keep_alive_ack(timestamp)
|
||||
else
|
||||
@@ -1002,9 +961,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
println_ts("linked!")
|
||||
log.info("supervisor establish request approved, PLC is linked")
|
||||
log.info("supervisor establish request approved, linked to SV (CID#" .. src_addr .. ")")
|
||||
|
||||
-- reset remote sequence number and cache
|
||||
-- link + reset remote sequence number and cache
|
||||
self.sv_addr = src_addr
|
||||
self.linked = true
|
||||
self.r_seq_num = nil
|
||||
self.status_cache = nil
|
||||
|
||||
@@ -1012,23 +973,28 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
|
||||
log.debug("sent initial status data")
|
||||
elseif self.last_est_ack ~= est_ack then
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
println_ts("link request denied, retrying...")
|
||||
log.info("supervisor establish request denied, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
println_ts("reactor PLC ID collision (check config), retrying...")
|
||||
log.warning("establish request collision, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
println_ts("supervisor version mismatch (try updating), retrying...")
|
||||
log.warning("establish request version mismatch, retrying")
|
||||
else
|
||||
println_ts("invalid link response, bad channel? retrying...")
|
||||
log.error("unknown establish request response, retrying")
|
||||
else
|
||||
if self.last_est_ack ~= est_ack then
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
println_ts("link request denied, retrying...")
|
||||
log.info("supervisor establish request denied, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
println_ts("reactor PLC ID collision (check config), retrying...")
|
||||
log.warning("establish request collision, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
println_ts("supervisor version mismatch (try updating), retrying...")
|
||||
log.warning("establish request version mismatch, retrying")
|
||||
else
|
||||
println_ts("invalid link response, bad channel? retrying...")
|
||||
log.error("unknown establish request response, retrying")
|
||||
end
|
||||
end
|
||||
|
||||
-- unlink
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.linked = false
|
||||
end
|
||||
|
||||
self.linked = est_ack == ESTABLISH_ACK.ALLOW
|
||||
self.last_est_ack = est_ack
|
||||
|
||||
-- report link state
|
||||
@@ -1044,7 +1010,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
log.error("illegal packet type " .. protocol, true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet on unconfigured channel " .. l_port, true)
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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.3.2"
|
||||
local R_PLC_VERSION = "v1.5.5"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@@ -31,8 +32,8 @@ local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_bool(config.NETWORKED)
|
||||
cfv.assert_type_int(config.REACTOR_ID)
|
||||
cfv.assert_port(config.SERVER_PORT)
|
||||
cfv.assert_port(config.LISTEN_PORT)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.PLC_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.COMMS_TIMEOUT)
|
||||
cfv.assert_min(config.COMMS_TIMEOUT, 2)
|
||||
@@ -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.LISTEN_PORT, config.SERVER_PORT,
|
||||
-- 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
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcallbackdsp = require("scada-common.tcallbackdsp")
|
||||
local util = require("scada-common.util")
|
||||
local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("reactor-plc.databus")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
local databus = require("reactor-plc.databus")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local core = require("graphics.core")
|
||||
|
||||
local threads = {}
|
||||
|
||||
@@ -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
|
||||
@@ -157,7 +159,7 @@ function threads.thread__main(smem, init)
|
||||
smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT)
|
||||
elseif event == "timer" then
|
||||
-- notify timer callback dispatcher if no other timer case claimed this event
|
||||
tcallbackdsp.handle(param1)
|
||||
tcd.handle(param1)
|
||||
elseif event == "peripheral_detach" then
|
||||
-- peripheral disconnect
|
||||
local type, device = ppm.handle_unmount(param1)
|
||||
@@ -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
|
||||
@@ -199,12 +208,11 @@ function threads.thread__main(smem, init)
|
||||
if 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
|
||||
|
||||
@@ -2,14 +2,18 @@ local rsio = require("scada-common.rsio")
|
||||
|
||||
local config = {}
|
||||
|
||||
-- port to send packets TO server
|
||||
config.SERVER_PORT = 16000
|
||||
-- port to listen to incoming packets FROM server
|
||||
config.LISTEN_PORT = 15001
|
||||
-- max trusted modem message distance (< 1 to disable check)
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- RTU/MODBUS comms channel
|
||||
config.RTU_CHANNEL = 16242
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
config.COMMS_TIMEOUT = 5
|
||||
-- facility authentication key (do NOT use one of your passwords)
|
||||
-- this enables verifying that messages are authentic
|
||||
-- all devices on the same network must use the same key
|
||||
-- config.AUTH_KEY = "SCADAfacility123"
|
||||
|
||||
-- log path
|
||||
config.LOG_PATH = "/log.txt"
|
||||
|
||||
@@ -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,7 +1,8 @@
|
||||
--
|
||||
-- Main SCADA Coordinator GUI
|
||||
-- RTU Front Panel GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("rtu.databus")
|
||||
@@ -25,6 +26,7 @@ local UNIT_TYPE_LABELS = {
|
||||
"REDSTONE",
|
||||
"BOILER",
|
||||
"TURBINE",
|
||||
"DYNAMIC TANK",
|
||||
"IND MATRIX",
|
||||
"SPS",
|
||||
"SNA",
|
||||
@@ -32,7 +34,7 @@ local UNIT_TYPE_LABELS = {
|
||||
}
|
||||
|
||||
|
||||
-- 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)
|
||||
@@ -44,7 +46,7 @@ local function init(panel, units)
|
||||
|
||||
local system = Div{parent=panel,width=14,height=18,x=2,y=3}
|
||||
|
||||
local on = LED{parent=system,label="POWER",colors=cpair(colors.green,colors.red)}
|
||||
local on = 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)}
|
||||
on.update(true)
|
||||
system.line_break()
|
||||
@@ -53,7 +55,7 @@ local function init(panel, units)
|
||||
|
||||
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(5)
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
system.line_break()
|
||||
|
||||
modem.register(databus.ps, "has_modem", modem.update)
|
||||
@@ -66,6 +68,10 @@ local function init(panel, units)
|
||||
rt_main.register(databus.ps, "routine__main", rt_main.update)
|
||||
rt_comm.register(databus.ps, "routine__comms", rt_comm.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)}
|
||||
|
||||
--
|
||||
-- about label
|
||||
--
|
||||
|
||||
@@ -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 },
|
||||
|
||||
57
rtu/rtu.lua
57
rtu/rtu.lua
@@ -158,13 +158,14 @@ end
|
||||
-- RTU Communications
|
||||
---@nodiscard
|
||||
---@param version string RTU version
|
||||
---@param modem table modem device
|
||||
---@param local_port integer local listening port
|
||||
---@param server_port integer remote server port
|
||||
---@param nic nic network interface device
|
||||
---@param rtu_channel integer PLC comms channel
|
||||
---@param svr_channel integer supervisor server channel
|
||||
---@param range integer trusted device connection range
|
||||
---@param conn_watchdog watchdog watchdog reference
|
||||
function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog)
|
||||
function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog)
|
||||
local self = {
|
||||
sv_addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
r_seq_num = nil,
|
||||
txn_id = 0,
|
||||
@@ -178,12 +179,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(local_port)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
nic.closeAll()
|
||||
nic.open(rtu_channel)
|
||||
|
||||
-- send a scada management packet
|
||||
---@param msg_type SCADA_MGMT_TYPE
|
||||
@@ -193,9 +190,9 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, rtu_channel, s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@@ -238,23 +235,18 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
---@param m_pkt modbus_packet
|
||||
function public.send_modbus(m_pkt)
|
||||
local s_pkt = comms.scada_packet()
|
||||
s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, rtu_channel, s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
-- reconnect a newly connected modem
|
||||
---@param new_modem table
|
||||
function public.reconnect_modem(new_modem)
|
||||
modem = new_modem
|
||||
_conf_channels()
|
||||
end
|
||||
|
||||
-- unlink from the server
|
||||
---@param rtu_state rtu_state
|
||||
function public.unlink(rtu_state)
|
||||
rtu_state.linked = false
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.r_seq_num = nil
|
||||
databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
end
|
||||
|
||||
-- close the connection to the server
|
||||
@@ -292,13 +284,10 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
---@param distance integer
|
||||
---@return modbus_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 MODBUS TCP packet
|
||||
if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then
|
||||
local m_pkt = comms.modbus_packet()
|
||||
@@ -327,13 +316,21 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println_ts(message) if not rtu_state.fp_ok then util.println_ts(message) end end
|
||||
|
||||
if packet.scada_frame.local_port() == local_port then
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
|
||||
if l_chan == rtu_channel then
|
||||
-- check sequence number
|
||||
if self.r_seq_num == nil then
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif rtu_state.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif rtu_state.linked and (src_addr ~= self.sv_addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
end
|
||||
@@ -341,8 +338,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- feed watchdog on valid sequence number
|
||||
conn_watchdog.feed()
|
||||
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
-- handle packet
|
||||
if protocol == PROTOCOL.MODBUS_TCP then
|
||||
---@cast packet modbus_frame
|
||||
if rtu_state.linked then
|
||||
@@ -398,6 +394,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
-- establish allowed
|
||||
rtu_state.linked = true
|
||||
self.sv_addr = packet.scada_frame.src_addr()
|
||||
self.r_seq_num = nil
|
||||
println_ts("supervisor connection established")
|
||||
log.info("supervisor connection established")
|
||||
@@ -461,6 +458,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- should be unreachable assuming packet is from parse_packet()
|
||||
log.error("illegal packet type " .. protocol, true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user