From 543385a88036c5b84f0021f828ea81704e56a2b0 Mon Sep 17 00:00:00 2001 From: Dystroyer8 Date: Thu, 16 Jul 2026 21:05:47 +0200 Subject: [PATCH] Read Create gauges through block readers --- README.md | 10 +- create_boiler_display.lua | 235 +++++++++++++++----------------------- 2 files changed, 97 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index bd4d31f..77c2e32 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,10 @@ werden ergaenzt, sobald die Peripherie- und Methodennamen der gebauten Anlagen f ## Einfache Create-Boiler-Anzeige -Fuer die erste Kraftwerksanzeige werden ein Monitor, ein Create-Speedometer und -ein Create-Stressometer an der gemeinsamen Hauptwelle sowie pro Boiler ein Block Reader aus -Advanced Peripherals ueber Wired Modems mit dem Computer verbunden. Installation und Start: +Fuer die erste Kraftwerksanzeige werden ein Monitor sowie pro Boiler ein Block Reader aus +Advanced Peripherals benoetigt. Mindestens ein weiterer Block Reader zeigt auf das Create- +Speedometer oder Stressometer der gemeinsamen Hauptwelle. Alle Reader und der Monitor werden +ueber Wired Modems mit dem Computer verbunden. Installation und Start: ```lua wget https://git.peli-server.de/Dystroyer8/ATM10_CC_Codes/raw/branch/main/create_boiler_display.lua create_boiler_display.lua @@ -31,7 +32,8 @@ create_boiler_display ``` Das Programm liest Online-Status und Heizmodus jedes Boilers ueber dessen Block Reader. -Drehzahl, Kapazitaet, Verbrauch und Auslastung werden fuer die gemeinsame Hauptwelle angezeigt. +Drehzahl, Kapazitaet, Verbrauch und Auslastung liest es aus den Netzwerkdaten des Gauge-Readers. +Die Monitoraufloesung wird automatisch erkannt und die groesste passende Textskalierung gewaehlt. Die Boiler-Zuordnung erfolgt nach den internen Peripheral-Namen; die drei Block Reader sollten deshalb in der Reihenfolge Boiler 1 bis 3 an das Wired Network angeschlossen werden. Jeder Block Reader muss auf den Controller-Tank des zugeordneten Boiler-Multiblocks zeigen. diff --git a/create_boiler_display.lua b/create_boiler_display.lua index 296d484..dd08fbe 100644 --- a/create_boiler_display.lua +++ b/create_boiler_display.lua @@ -1,25 +1,34 @@ -- Create boiler status board for CC:Tweaked. --- Hardware: --- * one monitor --- * one Create Speedometer on the shared main shaft --- * one Create Stressometer on the main shaft --- * one Advanced Peripherals Block Reader per boiler controller --- Everything must be connected to the computer, preferably using Wired Modems. +-- All Create values are read through Advanced Peripherals Block Readers. local EXPECTED_BOILERS = 3 local REFRESH_SECONDS = 1 -local MONITOR_SCALE = 0.5 -local SPEEDOMETER_TYPE = "Create_Speedometer" -local STRESSOMETER_TYPE = "Create_Stressometer" -local BLOCK_READER_TYPE = "block_reader" +local MIN_WIDTH = 38 +local MIN_HEIGHT = 11 local monitor = peripheral.find("monitor") if not monitor then - error("Kein Monitor gefunden. Monitor direkt anschliessen oder per Wired Modem verbinden.", 0) + error("Kein Monitor gefunden. Monitor direkt oder per Wired Modem verbinden.", 0) end -monitor.setTextScale(MONITOR_SCALE) -monitor.setCursorBlink(false) +local function fitMonitor() + local chosen = 0.5 + for step = 10, 1, -1 do + local scale = step / 2 + local ok = pcall(monitor.setTextScale, scale) + if ok then + local width, height = monitor.getSize() + if width >= MIN_WIDTH and height >= MIN_HEIGHT then + chosen = scale + break + end + end + end + monitor.setTextScale(chosen) + monitor.setCursorBlink(false) +end + +fitMonitor() local function center(y, text, foreground, background) local width = monitor.getSize() @@ -34,28 +43,6 @@ local function center(y, text, foreground, background) monitor.write(text) end -local function hasMethod(name, wantedMethod) - local methods = peripheral.getMethods(name) or {} - for _, method in ipairs(methods) do - if method == wantedMethod then return true end - end - return false -end - -local function getPeripherals(wantedType, requiredMethod) - local names = {} - for _, name in ipairs(peripheral.getNames()) do - local peripheralType = peripheral.getType(name) - local typeMatches = type(peripheralType) == "string" - and peripheralType:lower() == wantedType:lower() - if typeMatches or (requiredMethod and hasMethod(name, requiredMethod)) then - names[#names + 1] = name - end - end - table.sort(names) - return names -end - local function getValueIgnoringCase(data, wantedKeys) if type(data) ~= "table" then return nil end for key, value in pairs(data) do @@ -67,79 +54,71 @@ local function getValueIgnoringCase(data, wantedKeys) return nil end -local function findBoilerData(data, visited) - if type(data) ~= "table" then return nil end - visited = visited or {} - if visited[data] then return nil end - visited[data] = true - - local boiler = getValueIgnoringCase(data, { "Boiler" }) - if type(boiler) == "table" then return boiler end - - local heat = getValueIgnoringCase(data, { "Heat", "activeHeat", "actualHeat" }) - local passive = getValueIgnoringCase(data, { "PassiveHeat", "passive" }) - if heat ~= nil or passive ~= nil then return data end - - for _, value in pairs(data) do - local found = findBoilerData(value, visited) - if found then return found end - end - return nil +local function samePosition(a, b) + if type(a) ~= "table" or type(b) ~= "table" then return true end + return tonumber(a[1]) == tonumber(b[1]) + and tonumber(a[2]) == tonumber(b[2]) + and tonumber(a[3]) == tonumber(b[3]) end -local function readBoiler(readerName) - if not readerName then return "MISSING", "UNKNOWN", colors.red end - local ok, data = pcall(peripheral.call, readerName, "getBlockData") - if not ok then return "ERROR", "UNKNOWN", colors.red end +local function discoverReaders() + local boilers, networks = {}, {} + local names = peripheral.getNames() + table.sort(names) - local boiler = findBoilerData(data) - if not boiler then return "NO DATA", "UNKNOWN", colors.red end + for _, name in ipairs(names) do + if peripheral.hasType(name, "block_reader") then + local ok, data = pcall(peripheral.call, name, "getBlockData") + if ok and type(data) == "table" then + local boiler = getValueIgnoringCase(data, { "Boiler" }) + local network = getValueIgnoringCase(data, { "Network" }) + local speed = getValueIgnoringCase(data, { "Speed" }) - local passive = getValueIgnoringCase(boiler, { "PassiveHeat", "passive" }) - local heat = tonumber(getValueIgnoringCase(boiler, - { "Heat", "activeHeat", "actualHeat" })) - local supply = tonumber(getValueIgnoringCase(boiler, - { "Supply", "waterSupply", "gatheredSupply" })) - local engines = tonumber(getValueIgnoringCase(boiler, - { "Engines", "attachedEngines" })) - local active = getValueIgnoringCase(boiler, - { "Active", "isActive", "prevActive" }) - - local heatMode - if passive == true or passive == 1 then - heatMode = "CAMPFIRE" - elseif heat and heat >= 18 then - heatMode = "SUPERHEATED" - elseif heat and heat > 0 then - heatMode = "HEATED" - else - heatMode = "COLD" + if type(boiler) == "table" then + boilers[#boilers + 1] = { name = name, data = data, boiler = boiler } + elseif type(network) == "table" and tonumber(speed) then + networks[#networks + 1] = { name = name, data = data, network = network } + end + end + end end - - if active == nil then - local hasHeat = heatMode ~= "COLD" - local hasWater = supply == nil or supply > 0 - local hasEngines = engines == nil or engines > 0 - active = hasHeat and hasWater and hasEngines - end - - if active == true or active == 1 then - return "ONLINE", heatMode, colors.lime - end - return "STANDBY", heatMode, colors.orange + return boilers, networks end -local function readMainSpeed(speedometerName) - if not speedometerName then return nil end - local ok, speed = pcall(peripheral.call, speedometerName, "getSpeed") - if not ok or type(speed) ~= "number" then return nil end - return math.abs(speed) +local function readBoiler(entry) + if not entry then return "MISSING", "NO READER", colors.red end + + local controller = getValueIgnoringCase(entry.data, { "Controller" }) + local readerPos = getValueIgnoringCase(entry.data, { "LastKnownPos" }) + if not samePosition(controller, readerPos) then + return "READER", "WRONG TANK", colors.red + end + + local boiler = entry.boiler + local passive = tonumber(getValueIgnoringCase(boiler, { "PassiveHeat", "passive" })) or 0 + local heat = tonumber(getValueIgnoringCase(boiler, { "ActiveHeat", "Heat", "actualHeat" })) or 0 + local supply = tonumber(getValueIgnoringCase(boiler, { "Supply", "waterSupply" })) or 0 + local engines = tonumber(getValueIgnoringCase(boiler, { "Engines", "attachedEngines" })) or 0 + + if passive > 0 then return "ONLINE", "CAMPFIRE", colors.lime end + if heat >= 18 then return "ONLINE", "SUPERHEATED", colors.lime end + if heat > 0 then return "ONLINE", "HEATED", colors.lime end + if engines > 0 and supply > 0 then return "STANDBY", "NO HEAT", colors.orange end + return "STANDBY", "COLD", colors.orange +end + +local function readMainShaft(entry) + if not entry then return nil, nil, nil, nil end + local speed = math.abs(tonumber(getValueIgnoringCase(entry.data, { "Speed" })) or 0) + local capacity = tonumber(getValueIgnoringCase(entry.network, { "Capacity" })) or 0 + local usage = tonumber(getValueIgnoringCase(entry.network, { "Stress" })) or 0 + local load = capacity > 0 and usage / capacity * 100 or 0 + return speed, capacity, usage, load end local function formatInteger(value) value = math.floor(math.abs(tonumber(value) or 0) + 0.5) - local digits = tostring(value) - local parts = {} + local digits, parts = tostring(value), {} while #digits > 3 do table.insert(parts, 1, digits:sub(-3)) digits = digits:sub(1, -4) @@ -148,65 +127,33 @@ local function formatInteger(value) return table.concat(parts, ".") end -local function readMainShaft(stressometerName) - if not stressometerName then return nil, nil, nil end - local capacityMethod = hasMethod(stressometerName, "getStressCapacity") - and "getStressCapacity" or "getNetworkCapacity" - local usageMethod = hasMethod(stressometerName, "getStress") - and "getStress" or "getNetworkStress" - local okCapacity, capacity = pcall(peripheral.call, stressometerName, capacityMethod) - local okUsage, usage = pcall(peripheral.call, stressometerName, usageMethod) - if not okCapacity or not okUsage then return nil, nil, nil end - capacity = tonumber(capacity) or 0 - usage = tonumber(usage) or 0 - local load = capacity > 0 and usage / capacity * 100 or 0 - return capacity, usage, load -end - local function render() local _, height = monitor.getSize() - local speedometers = getPeripherals(SPEEDOMETER_TYPE, "getSpeed") - local stressometers = getPeripherals(STRESSOMETER_TYPE, "getStress") - local readers = getPeripherals(BLOCK_READER_TYPE, "getBlockData") + local boilers, networks = discoverReaders() + local speed, capacity, usage, load = readMainShaft(networks[1]) monitor.setBackgroundColor(colors.black) monitor.clear() center(1, "CREATE KRAFTWERK", colors.cyan) - local firstRow = 3 + for index = 1, EXPECTED_BOILERS do - local state, heatMode, color = readBoiler(readers[index]) - local row = firstRow + index - 1 - if row <= height then - center(row, ("BOILER %d %-7s %s") - :format(index, state, heatMode), color) - end + local state, heatMode, color = readBoiler(boilers[index]) + center(2 + index, ("BOILER %d %-7s %s"):format(index, state, heatMode), color) end - local speed = readMainSpeed(speedometers[1]) - local capacity, usage, load = readMainShaft(stressometers[1]) - if height >= 7 then center(7, "MAIN SHAFT", colors.cyan) end - if height >= 8 then - if speed then - center(8, ("Speed: %.1f RPM"):format(speed), speed > 0 and colors.lime or colors.orange) - else - center(8, "Speedometer nicht gefunden", colors.red) - end - end - if capacity then - if height >= 9 then center(9, "Capacity: " .. formatInteger(capacity) .. " SU", colors.white) end - if height >= 10 then center(10, "Usage: " .. formatInteger(usage) .. " SU", colors.white) end - if height >= 11 then - local loadText = ("Load: %.1f %%"):format(load):gsub("%.", ",") - center(11, loadText, load >= 90 and colors.red or load >= 70 and colors.orange or colors.lime) - end + center(7, "MAIN SHAFT", colors.cyan) + if speed then + center(8, ("Speed: %.1f RPM"):format(speed), speed > 0 and colors.lime or colors.orange) + center(9, "Capacity: " .. formatInteger(capacity) .. " SU", colors.white) + center(10, "Usage: " .. formatInteger(usage) .. " SU", colors.white) + local loadText = ("Load: %.1f %%"):format(load):gsub("%.", ",") + center(11, loadText, load >= 90 and colors.red or load >= 70 and colors.orange or colors.lime) else - if height >= 9 then center(9, "Stressometer nicht gefunden", colors.red) end - end - - if height >= 13 then - center(height, "Nur Anzeige - keine Steuerung", colors.gray) + center(8, "Kein Gauge-Reader gefunden", colors.red) + center(9, "Reader auf Speed-/Stressometer", colors.orange) end + if height >= 13 then center(height, "Nur Anzeige - keine Steuerung", colors.gray) end monitor.setTextColor(colors.white) monitor.setBackgroundColor(colors.black) term.setCursorPos(1, 1)