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
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.
+91 -144
View File
@@ -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)