commit 5362d31036b682835cf70e7d0d743d26f45c5203 Author: Dystroyer8 Date: Mon Jul 13 02:46:11 2026 +0200 Initialize modular ATM10 CC control system diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69039f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.tmp +*.bak +runtime/ +config/local.lua + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a2fc9f --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# ATM10 CC Control System + +Modulares CC:Tweaked-Steuerungssystem fuer die ATM10-Megabase. + +## Zielbild + +- zentrale Leitwarte fuer Status, Energie, Alarme und Sektor-Hauptschalter +- lokale Sektorcontroller fuer Create, Mekanism, Greenhouse und spaetere Mods +- Sicherheitslogik bleibt immer im jeweiligen Sektor +- versionierte Installation und Updates ueber Gitea + +## Installation auf einem CC-Computer + +```lua +wget https://git.peli-server.de/Dystroyer8/ATM10_CC_Codes/raw/branch/main/install.lua install.lua +install +``` + +Der Installer ist momentan ein Bootstrap. Hardwaretreiber und konkrete Anlagenlogik +werden ergaenzt, sobald die Peripherie- und Methodennamen der gebauten Anlagen feststehen. + +## Struktur + +```text +core/ Laufzeit, Netzwerk, Protokoll, Logging, Updates +drivers/ Adapter fuer Redstone und Mod-Peripherie +roles/ Zentrale und Sektorcontroller +ui/ Monitore, Widgets und Alarmdarstellung +config/ Konfigurationsvorlagen +docs/ Architektur- und Protokolldokumentation +``` + +## Sicherheitsprinzip + +Die Zentrale fordert nur einen Zielzustand an. Lokale Controller fuehren sichere +Start-/Stoppsequenzen aus und bestaetigen den tatsaechlichen Zustand. + diff --git a/config/default.lua b/config/default.lua new file mode 100644 index 0000000..cc16a15 --- /dev/null +++ b/config/default.lua @@ -0,0 +1,9 @@ +return { + role = "main_control", + nodeName = "atm10-main", + sector = "main", + monitor = nil, + heartbeatSeconds = 5, + commandTimeoutSeconds = 10, +} + diff --git a/config/example.lua b/config/example.lua new file mode 100644 index 0000000..cc16a15 --- /dev/null +++ b/config/example.lua @@ -0,0 +1,9 @@ +return { + role = "main_control", + nodeName = "atm10-main", + sector = "main", + monitor = nil, + heartbeatSeconds = 5, + commandTimeoutSeconds = 10, +} + diff --git a/core/constants.lua b/core/constants.lua new file mode 100644 index 0000000..910a530 --- /dev/null +++ b/core/constants.lua @@ -0,0 +1,10 @@ +return { + PROTOCOL = "atm10-control", + PROTOCOL_VERSION = 1, + STATES = { + OFFLINE = "OFFLINE", STARTING = "STARTING", RUNNING = "RUNNING", + IDLE = "IDLE", STOPPING = "STOPPING", MAINTENANCE = "MAINTENANCE", + FAULT = "FAULT", EMERGENCY = "EMERGENCY", UNLOADED = "UNLOADED", + }, +} + diff --git a/core/log.lua b/core/log.lua new file mode 100644 index 0000000..64b36d8 --- /dev/null +++ b/core/log.lua @@ -0,0 +1,12 @@ +local M = {} +function M.write(level, message) + local line = ("[%s] %-5s %s"):format(os.date("!%Y-%m-%dT%H:%M:%SZ"), level, tostring(message)) + print(line) + local handle = fs.open("runtime/control.log", "a") + if handle then handle.writeLine(line); handle.close() end +end +function M.info(message) M.write("INFO", message) end +function M.warn(message) M.write("WARN", message) end +function M.error(message) M.write("ERROR", message) end +return M + diff --git a/core/network.lua b/core/network.lua new file mode 100644 index 0000000..04b5025 --- /dev/null +++ b/core/network.lua @@ -0,0 +1,18 @@ +local constants = require("core.constants") +local protocol = require("core.protocol") +local M = {} +function M.open() + for _, name in ipairs(peripheral.getNames()) do + if peripheral.hasType(name, "modem") then rednet.open(name) end + end + return rednet.isOpen() +end +function M.broadcast(message) rednet.broadcast(message, constants.PROTOCOL) end +function M.send(id, message) return rednet.send(id, message, constants.PROTOCOL) end +function M.receive(timeout) + local sender, message = rednet.receive(constants.PROTOCOL, timeout) + if sender and protocol.valid(message) then return sender, message end + return nil, nil +end +return M + diff --git a/core/protocol.lua b/core/protocol.lua new file mode 100644 index 0000000..564b80c --- /dev/null +++ b/core/protocol.lua @@ -0,0 +1,22 @@ +local constants = require("core.constants") +local M = {} +function M.message(kind, source, target, payload) + return { + protocol = constants.PROTOCOL, + version = constants.PROTOCOL_VERSION, + type = kind, + source = source, + target = target, + requestId = ("%s-%d-%d"):format(source, os.getComputerID(), os.epoch("utc")), + timestamp = os.epoch("utc"), + payload = payload or {}, + } +end +function M.valid(message) + return type(message) == "table" + and message.protocol == constants.PROTOCOL + and message.version == constants.PROTOCOL_VERSION + and type(message.type) == "string" +end +return M + diff --git a/core/runtime.lua b/core/runtime.lua new file mode 100644 index 0000000..a08676d --- /dev/null +++ b/core/runtime.lua @@ -0,0 +1,19 @@ +local log = require("core.log") +local network = require("core.network") + +local M = {} +local function loadConfig() + if not fs.exists("config/local.lua") then error("config/local.lua fehlt") end + return dofile("config/local.lua") +end +function M.run() + if not fs.exists("runtime") then fs.makeDir("runtime") end + local config = loadConfig() + os.setComputerLabel(config.nodeName) + network.open() + local role = require("roles." .. config.role) + log.info("Starte " .. config.nodeName .. " als " .. config.role) + role.run(config) +end +return M + diff --git a/core/updater.lua b/core/updater.lua new file mode 100644 index 0000000..a3aff52 --- /dev/null +++ b/core/updater.lua @@ -0,0 +1,6 @@ +local M = {} +function M.run() + print("Updater-Schnittstelle vorbereitet; atomisches Update folgt vor v0.1.0.") +end +return M + diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..3510992 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,18 @@ +# Architektur + +## Verantwortlichkeiten + +- Hauptzentrale: Anzeige, Befehle, Alarmaggregation, Historie +- Sektorcontroller: Start-/Stoppsequenzen, lokale Interlocks, Teilanlagen +- Anlagencontroller: direkte Peripherie, Messwerte und Aktoren + +## Grundsatz + +Ein zentraler Befehl beschreibt einen gewuenschten Zustand. Nur der lokale +Controller entscheidet, wie dieser Zustand sicher erreicht wird. + +## Sektorzustaende + +`OFFLINE`, `STARTING`, `RUNNING`, `IDLE`, `STOPPING`, `MAINTENANCE`, +`FAULT`, `EMERGENCY`, `UNLOADED`. + diff --git a/docs/protocol.md b/docs/protocol.md new file mode 100644 index 0000000..3a01c71 --- /dev/null +++ b/docs/protocol.md @@ -0,0 +1,18 @@ +# Netzwerkprotokoll v1 + +Alle Rednet-Nachrichten verwenden das Protokoll `atm10-control`. + +Pflichtfelder: + +- `protocol` +- `version` +- `type` +- `source` +- `target` +- `requestId` +- `timestamp` +- `payload` + +Vorgesehene Nachrichtentypen: `HEARTBEAT`, `STATUS`, `COMMAND`, `ACK`, +`PROGRESS`, `ALARM`, `CLEAR_ALARM`. + diff --git a/drivers/create.lua b/drivers/create.lua new file mode 100644 index 0000000..92a3d84 --- /dev/null +++ b/drivers/create.lua @@ -0,0 +1,2 @@ +return { probe = function() return nil, "create driver not configured" end } + diff --git a/drivers/energy.lua b/drivers/energy.lua new file mode 100644 index 0000000..e4f0628 --- /dev/null +++ b/drivers/energy.lua @@ -0,0 +1,2 @@ +return { probe = function() return nil, "energy driver not configured" end } + diff --git a/drivers/inventory.lua b/drivers/inventory.lua new file mode 100644 index 0000000..b9ed0c5 --- /dev/null +++ b/drivers/inventory.lua @@ -0,0 +1,2 @@ +return { probe = function() return nil, "inventory driver not configured" end } + diff --git a/drivers/mekanism.lua b/drivers/mekanism.lua new file mode 100644 index 0000000..ceeb9d8 --- /dev/null +++ b/drivers/mekanism.lua @@ -0,0 +1,2 @@ +return { probe = function() return nil, "mekanism driver not configured" end } + diff --git a/drivers/redstone.lua b/drivers/redstone.lua new file mode 100644 index 0000000..f2cd2e5 --- /dev/null +++ b/drivers/redstone.lua @@ -0,0 +1,5 @@ +local M = {} +function M.set(side, enabled) redstone.setOutput(side, enabled == true) end +function M.get(side) return redstone.getInput(side) end +return M + diff --git a/install.lua b/install.lua new file mode 100644 index 0000000..e9c47ba --- /dev/null +++ b/install.lua @@ -0,0 +1,44 @@ +local BASE_URL = "https://git.peli-server.de/Dystroyer8/ATM10_CC_Codes/raw/branch/main/" + +local function fail(message) + printError(message) + error(message, 0) +end + +local function fetch(path) + local response, reason = http.get(BASE_URL .. path) + if not response then fail("Download fehlgeschlagen: " .. path .. " (" .. tostring(reason) .. ")") end + local body = response.readAll() + response.close() + return body +end + +local function writeFile(path, content) + local directory = fs.getDir(path) + if directory ~= "" and not fs.exists(directory) then fs.makeDir(directory) end + local temporary = path .. ".tmp" + local handle = assert(fs.open(temporary, "w")) + handle.write(content) + handle.close() + if fs.exists(path) then fs.delete(path) end + fs.move(temporary, path) +end + +print("ATM10 Control bootstrap") +local manifestText = fetch("manifest.lua") +local manifestFn, loadError = load(manifestText, "manifest", "t", {}) +if not manifestFn then fail("Manifest ungueltig: " .. tostring(loadError)) end +local manifest = manifestFn() + +for index, path in ipairs(manifest.files) do + write(("[%d/%d] %s"):format(index, #manifest.files, path)) + writeFile(path, fetch(path)) + print(" OK") +end + +if not fs.exists("config/local.lua") then + writeFile("config/local.lua", fetch("config/example.lua")) +end +writeFile(".atm10-version", manifest.version .. "\n") +print("Installation abgeschlossen. Konfiguriere config/local.lua und starte neu.") + diff --git a/manifest.lua b/manifest.lua new file mode 100644 index 0000000..a7f0f9c --- /dev/null +++ b/manifest.lua @@ -0,0 +1,29 @@ +return { + name = "atm10-control", + version = "0.1.0-dev", + channel = "main", + files = { + "startup.lua", + "core/constants.lua", + "core/log.lua", + "core/protocol.lua", + "core/network.lua", + "core/runtime.lua", + "core/updater.lua", + "drivers/redstone.lua", + "drivers/energy.lua", + "drivers/mekanism.lua", + "drivers/create.lua", + "drivers/inventory.lua", + "ui/colors.lua", + "ui/widgets.lua", + "ui/dashboard.lua", + "ui/alarms.lua", + "roles/main_control.lua", + "roles/create_control.lua", + "roles/mekanism_control.lua", + "roles/greenhouse_control.lua", + "config/default.lua", + }, +} + diff --git a/roles/create_control.lua b/roles/create_control.lua new file mode 100644 index 0000000..6ee086b --- /dev/null +++ b/roles/create_control.lua @@ -0,0 +1,8 @@ +local dashboard = require("ui.dashboard") +local M = {} +function M.run(config) + dashboard.render(term, "CREATE CONTROL", { "Bootstrap aktiv", "Sector: " .. config.sector }) + while true do os.pullEvent() end +end +return M + diff --git a/roles/greenhouse_control.lua b/roles/greenhouse_control.lua new file mode 100644 index 0000000..c7af6e8 --- /dev/null +++ b/roles/greenhouse_control.lua @@ -0,0 +1,8 @@ +local dashboard = require("ui.dashboard") +local M = {} +function M.run(config) + dashboard.render(term, "GREENHOUSE CONTROL", { "Bootstrap aktiv", "Sector: " .. config.sector }) + while true do os.pullEvent() end +end +return M + diff --git a/roles/main_control.lua b/roles/main_control.lua new file mode 100644 index 0000000..67791d1 --- /dev/null +++ b/roles/main_control.lua @@ -0,0 +1,8 @@ +local dashboard = require("ui.dashboard") +local M = {} +function M.run(config) + dashboard.render(term, "ATM10 MAIN CONTROL", { "Bootstrap aktiv", "Node: " .. config.nodeName }) + while true do os.pullEvent() end +end +return M + diff --git a/roles/mekanism_control.lua b/roles/mekanism_control.lua new file mode 100644 index 0000000..af58d37 --- /dev/null +++ b/roles/mekanism_control.lua @@ -0,0 +1,8 @@ +local dashboard = require("ui.dashboard") +local M = {} +function M.run(config) + dashboard.render(term, "MEKANISM CONTROL", { "Bootstrap aktiv", "Sector: " .. config.sector }) + while true do os.pullEvent() end +end +return M + diff --git a/startup.lua b/startup.lua new file mode 100644 index 0000000..9f6d395 --- /dev/null +++ b/startup.lua @@ -0,0 +1,7 @@ +local ok, runtime = pcall(require, "core.runtime") +if not ok then + printError("ATM10 Control konnte nicht starten: " .. tostring(runtime)) + return +end +runtime.run() + diff --git a/ui/alarms.lua b/ui/alarms.lua new file mode 100644 index 0000000..3d7e7c4 --- /dev/null +++ b/ui/alarms.lua @@ -0,0 +1,2 @@ +return { active = function() return {} end } + diff --git a/ui/colors.lua b/ui/colors.lua new file mode 100644 index 0000000..e5bf881 --- /dev/null +++ b/ui/colors.lua @@ -0,0 +1,2 @@ +return { background = colors.black, text = colors.white, ok = colors.lime, warning = colors.orange, fault = colors.red, accent = colors.cyan } + diff --git a/ui/dashboard.lua b/ui/dashboard.lua new file mode 100644 index 0000000..de020ff --- /dev/null +++ b/ui/dashboard.lua @@ -0,0 +1,11 @@ +local widgets = require("ui.widgets") +local M = {} +function M.render(target, title, lines) + target.setBackgroundColor(colors.black) + target.setTextColor(colors.white) + target.clear() + widgets.title(target, title) + for index, line in ipairs(lines or {}) do target.setCursorPos(1, index + 2); target.write(line) end +end +return M + diff --git a/ui/widgets.lua b/ui/widgets.lua new file mode 100644 index 0000000..78659e1 --- /dev/null +++ b/ui/widgets.lua @@ -0,0 +1,8 @@ +local M = {} +function M.title(target, text) + target.setCursorPos(1, 1) + target.clearLine() + target.write(text) +end +return M +