Read Create gauges through block readers

This commit is contained in:
2026-07-16 21:05:47 +02:00
parent d586491a07
commit 543385a880
2 changed files with 97 additions and 148 deletions
+6 -4
View File
@@ -21,9 +21,10 @@ werden ergaenzt, sobald die Peripherie- und Methodennamen der gebauten Anlagen f
## Einfache Create-Boiler-Anzeige ## Einfache Create-Boiler-Anzeige
Fuer die erste Kraftwerksanzeige werden ein Monitor, ein Create-Speedometer und Fuer die erste Kraftwerksanzeige werden ein Monitor sowie pro Boiler ein Block Reader aus
ein Create-Stressometer an der gemeinsamen Hauptwelle sowie pro Boiler ein Block Reader aus Advanced Peripherals benoetigt. Mindestens ein weiterer Block Reader zeigt auf das Create-
Advanced Peripherals ueber Wired Modems mit dem Computer verbunden. Installation und Start: Speedometer oder Stressometer der gemeinsamen Hauptwelle. Alle Reader und der Monitor werden
ueber Wired Modems mit dem Computer verbunden. Installation und Start:
```lua ```lua
wget https://git.peli-server.de/Dystroyer8/ATM10_CC_Codes/raw/branch/main/create_boiler_display.lua create_boiler_display.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. 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 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 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. Reader muss auf den Controller-Tank des zugeordneten Boiler-Multiblocks zeigen.
+82 -135
View File
@@ -1,25 +1,34 @@
-- Create boiler status board for CC:Tweaked. -- Create boiler status board for CC:Tweaked.
-- Hardware: -- All Create values are read through Advanced Peripherals Block Readers.
-- * 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.
local EXPECTED_BOILERS = 3 local EXPECTED_BOILERS = 3
local REFRESH_SECONDS = 1 local REFRESH_SECONDS = 1
local MONITOR_SCALE = 0.5 local MIN_WIDTH = 38
local SPEEDOMETER_TYPE = "Create_Speedometer" local MIN_HEIGHT = 11
local STRESSOMETER_TYPE = "Create_Stressometer"
local BLOCK_READER_TYPE = "block_reader"
local monitor = peripheral.find("monitor") local monitor = peripheral.find("monitor")
if not monitor then 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 end
monitor.setTextScale(MONITOR_SCALE) 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) monitor.setCursorBlink(false)
end
fitMonitor()
local function center(y, text, foreground, background) local function center(y, text, foreground, background)
local width = monitor.getSize() local width = monitor.getSize()
@@ -34,28 +43,6 @@ local function center(y, text, foreground, background)
monitor.write(text) monitor.write(text)
end 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) local function getValueIgnoringCase(data, wantedKeys)
if type(data) ~= "table" then return nil end if type(data) ~= "table" then return nil end
for key, value in pairs(data) do for key, value in pairs(data) do
@@ -67,79 +54,71 @@ local function getValueIgnoringCase(data, wantedKeys)
return nil return nil
end end
local function findBoilerData(data, visited) local function samePosition(a, b)
if type(data) ~= "table" then return nil end if type(a) ~= "table" or type(b) ~= "table" then return true end
visited = visited or {} return tonumber(a[1]) == tonumber(b[1])
if visited[data] then return nil end and tonumber(a[2]) == tonumber(b[2])
visited[data] = true and tonumber(a[3]) == tonumber(b[3])
end
local function discoverReaders()
local boilers, networks = {}, {}
local names = peripheral.getNames()
table.sort(names)
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 boiler = getValueIgnoringCase(data, { "Boiler" })
if type(boiler) == "table" then return boiler end local network = getValueIgnoringCase(data, { "Network" })
local speed = getValueIgnoringCase(data, { "Speed" })
local heat = getValueIgnoringCase(data, { "Heat", "activeHeat", "actualHeat" }) if type(boiler) == "table" then
local passive = getValueIgnoringCase(data, { "PassiveHeat", "passive" }) boilers[#boilers + 1] = { name = name, data = data, boiler = boiler }
if heat ~= nil or passive ~= nil then return data end elseif type(network) == "table" and tonumber(speed) then
networks[#networks + 1] = { name = name, data = data, network = network }
for _, value in pairs(data) do
local found = findBoilerData(value, visited)
if found then return found end
end end
return nil end
end
end
return boilers, networks
end end
local function readBoiler(readerName) local function readBoiler(entry)
if not readerName then return "MISSING", "UNKNOWN", colors.red end if not entry then return "MISSING", "NO READER", colors.red end
local ok, data = pcall(peripheral.call, readerName, "getBlockData")
if not ok then return "ERROR", "UNKNOWN", colors.red end
local boiler = findBoilerData(data) local controller = getValueIgnoringCase(entry.data, { "Controller" })
if not boiler then return "NO DATA", "UNKNOWN", colors.red end local readerPos = getValueIgnoringCase(entry.data, { "LastKnownPos" })
if not samePosition(controller, readerPos) then
local passive = getValueIgnoringCase(boiler, { "PassiveHeat", "passive" }) return "READER", "WRONG TANK", colors.red
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"
end end
if active == nil then local boiler = entry.boiler
local hasHeat = heatMode ~= "COLD" local passive = tonumber(getValueIgnoringCase(boiler, { "PassiveHeat", "passive" })) or 0
local hasWater = supply == nil or supply > 0 local heat = tonumber(getValueIgnoringCase(boiler, { "ActiveHeat", "Heat", "actualHeat" })) or 0
local hasEngines = engines == nil or engines > 0 local supply = tonumber(getValueIgnoringCase(boiler, { "Supply", "waterSupply" })) or 0
active = hasHeat and hasWater and hasEngines 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 end
if active == true or active == 1 then local function readMainShaft(entry)
return "ONLINE", heatMode, colors.lime if not entry then return nil, nil, nil, nil end
end local speed = math.abs(tonumber(getValueIgnoringCase(entry.data, { "Speed" })) or 0)
return "STANDBY", heatMode, colors.orange local capacity = tonumber(getValueIgnoringCase(entry.network, { "Capacity" })) or 0
end local usage = tonumber(getValueIgnoringCase(entry.network, { "Stress" })) or 0
local load = capacity > 0 and usage / capacity * 100 or 0
local function readMainSpeed(speedometerName) return speed, capacity, usage, load
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)
end end
local function formatInteger(value) local function formatInteger(value)
value = math.floor(math.abs(tonumber(value) or 0) + 0.5) value = math.floor(math.abs(tonumber(value) or 0) + 0.5)
local digits = tostring(value) local digits, parts = tostring(value), {}
local parts = {}
while #digits > 3 do while #digits > 3 do
table.insert(parts, 1, digits:sub(-3)) table.insert(parts, 1, digits:sub(-3))
digits = digits:sub(1, -4) digits = digits:sub(1, -4)
@@ -148,65 +127,33 @@ local function formatInteger(value)
return table.concat(parts, ".") return table.concat(parts, ".")
end 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 function render()
local _, height = monitor.getSize() local _, height = monitor.getSize()
local speedometers = getPeripherals(SPEEDOMETER_TYPE, "getSpeed") local boilers, networks = discoverReaders()
local stressometers = getPeripherals(STRESSOMETER_TYPE, "getStress") local speed, capacity, usage, load = readMainShaft(networks[1])
local readers = getPeripherals(BLOCK_READER_TYPE, "getBlockData")
monitor.setBackgroundColor(colors.black) monitor.setBackgroundColor(colors.black)
monitor.clear() monitor.clear()
center(1, "CREATE KRAFTWERK", colors.cyan) center(1, "CREATE KRAFTWERK", colors.cyan)
local firstRow = 3
for index = 1, EXPECTED_BOILERS do for index = 1, EXPECTED_BOILERS do
local state, heatMode, color = readBoiler(readers[index]) local state, heatMode, color = readBoiler(boilers[index])
local row = firstRow + index - 1 center(2 + index, ("BOILER %d %-7s %s"):format(index, state, heatMode), color)
if row <= height then
center(row, ("BOILER %d %-7s %s")
:format(index, state, heatMode), color)
end
end end
local speed = readMainSpeed(speedometers[1]) center(7, "MAIN SHAFT", colors.cyan)
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 if speed then
center(8, ("Speed: %.1f RPM"):format(speed), speed > 0 and colors.lime or colors.orange) center(8, ("Speed: %.1f RPM"):format(speed), speed > 0 and colors.lime or colors.orange)
else center(9, "Capacity: " .. formatInteger(capacity) .. " SU", colors.white)
center(8, "Speedometer nicht gefunden", colors.red) center(10, "Usage: " .. formatInteger(usage) .. " SU", colors.white)
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("%.", ",") local loadText = ("Load: %.1f %%"):format(load):gsub("%.", ",")
center(11, loadText, load >= 90 and colors.red or load >= 70 and colors.orange or colors.lime) center(11, loadText, load >= 90 and colors.red or load >= 70 and colors.orange or colors.lime)
end
else else
if height >= 9 then center(9, "Stressometer nicht gefunden", colors.red) end center(8, "Kein Gauge-Reader gefunden", colors.red)
end center(9, "Reader auf Speed-/Stressometer", colors.orange)
if height >= 13 then
center(height, "Nur Anzeige - keine Steuerung", colors.gray)
end end
if height >= 13 then center(height, "Nur Anzeige - keine Steuerung", colors.gray) end
monitor.setTextColor(colors.white) monitor.setTextColor(colors.white)
monitor.setBackgroundColor(colors.black) monitor.setBackgroundColor(colors.black)
term.setCursorPos(1, 1) term.setCursorPos(1, 1)