-- Create boiler status board for CC:Tweaked. -- Hardware: -- * one monitor -- * one Create Speedometer per boiler -- * one Create Stressometer on the main shaft -- * optionally one Advanced Peripherals Block Reader per boiler controller -- Everything must be connected to the computer, preferably using Wired Modems. 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 monitor = peripheral.find("monitor") if not monitor then error("Kein Monitor gefunden. Monitor direkt anschliessen oder per Wired Modem verbinden.", 0) end monitor.setTextScale(MONITOR_SCALE) monitor.setCursorBlink(false) local function center(y, text, foreground, background) local width = monitor.getSize() text = tostring(text) if #text > width then text = text:sub(1, width) end local x = math.max(1, math.floor((width - #text) / 2) + 1) monitor.setBackgroundColor(background or colors.black) monitor.setTextColor(foreground or colors.white) monitor.setCursorPos(1, y) monitor.write((" "):rep(width)) monitor.setCursorPos(x, y) monitor.write(text) end local function getPeripheralsByType(wantedType) local names = {} for _, name in ipairs(peripheral.getNames()) do if peripheral.getType(name) == wantedType 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 local lowered = tostring(key):lower() for _, wanted in ipairs(wantedKeys) do if lowered == wanted:lower() then return value end end end 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 end local function readHeatMode(readerName) if not readerName then return "UNKNOWN" end local ok, data = pcall(peripheral.call, readerName, "getBlockData") if not ok then return "ERROR" end local boiler = findBoilerData(data) if not boiler then return "UNKNOWN" end local passive = getValueIgnoringCase(boiler, { "PassiveHeat", "passive" }) local heat = tonumber(getValueIgnoringCase(boiler, { "Heat", "activeHeat", "actualHeat", "boilerLevel", "level" })) if passive == true or passive == 1 then return "CAMPFIRE" end if heat and heat >= 18 then return "SUPERHEATED" end if heat and heat > 0 then return "HEATED" end return "COLD" end local function readBoiler(name, readerName) if not name then return "MISSING", 0, "UNKNOWN", colors.red end local ok, speed = pcall(peripheral.call, name, "getSpeed") if not ok or type(speed) ~= "number" then return "ERROR", 0, "UNKNOWN", colors.red end speed = math.abs(speed) if speed < 0.5 then return "STANDBY", 0, readHeatMode(readerName), colors.orange end return "ONLINE", speed, readHeatMode(readerName), colors.lime end local function formatInteger(value) value = math.floor(math.abs(tonumber(value) or 0) + 0.5) local digits = tostring(value) local parts = {} while #digits > 3 do table.insert(parts, 1, digits:sub(-3)) digits = digits:sub(1, -4) end table.insert(parts, 1, digits) return table.concat(parts, ".") end local function readMainShaft(stressometerName) if not stressometerName then return nil, nil, nil end local okCapacity, capacity = pcall(peripheral.call, stressometerName, "getNetworkCapacity") local okUsage, usage = pcall(peripheral.call, stressometerName, "getNetworkStress") 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 = getPeripheralsByType(SPEEDOMETER_TYPE) local stressometers = getPeripheralsByType(STRESSOMETER_TYPE) local readers = getPeripheralsByType(BLOCK_READER_TYPE) monitor.setBackgroundColor(colors.black) monitor.clear() center(1, "CREATE KRAFTWERK", colors.cyan) local firstRow = 3 for index = 1, EXPECTED_BOILERS do local state, speed, heatMode, color = readBoiler(speedometers[index], readers[index]) local row = firstRow + index - 1 if row <= height then center(row, ("BOILER %d %-7s %5.1f RPM %s") :format(index, state, speed, heatMode), color) end end local capacity, usage, load = readMainShaft(stressometers[1]) if height >= 7 then center(7, "MAIN SHAFT", colors.cyan) end if capacity then if height >= 8 then center(8, "Capacity: " .. formatInteger(capacity) .. " SU", colors.white) end if height >= 9 then center(9, "Usage: " .. formatInteger(usage) .. " SU", colors.white) end if height >= 10 then local loadText = ("Load: %.1f %%"):format(load):gsub("%.", ",") center(10, loadText, load >= 90 and colors.red or load >= 70 and colors.orange or colors.lime) end else if height >= 8 then center(8, "Stressometer nicht gefunden", colors.red) end end if height >= 12 then center(height, "Nur Anzeige - keine Steuerung", colors.gray) end monitor.setTextColor(colors.white) monitor.setBackgroundColor(colors.black) term.setCursorPos(1, 1) end while true do local ok, reason = pcall(render) if not ok then monitor.setBackgroundColor(colors.black) monitor.clear() center(1, "ANZEIGEFEHLER", colors.red) center(3, tostring(reason), colors.orange) end sleep(REFRESH_SECONDS) end