zamoth

energy graph monitor

Jul 5th, 2025 (edited)
361
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 7.91 KB | None | 0 0
  1. -- graph_ui.lua  (run on your graph‐UI computer)
  2.  
  3. -- Peripheral wrapping & term redirect
  4. rednet.open("left")
  5. local mon = assert(peripheral.wrap("monitor_4"), "Monitor not found")
  6. term.redirect(mon)
  7.  
  8. -- Configuration
  9. local HISTORY_MAX = 100    -- number of data points to keep
  10. local INTERVAL    = 1     -- seconds between samples
  11.  
  12. -- histories
  13. local feHistory  = {}
  14. local pctHistory = {}
  15.  
  16. -- Subpixel hi-res line helpers (2x2 per char cell)
  17. local subpixelGrid = {}
  18.  
  19. local function setSubpixel(gx, gy, col)
  20.   local cx = math.floor((gx-1)/2)+1
  21.   local cy = math.floor((gy-1)/2)+1
  22.   local sx = (gx-1)%2
  23.   local sy = (gy-1)%2
  24.   local bit = sy*2 + sx -- 0..3
  25.   subpixelGrid[cy] = subpixelGrid[cy] or {}
  26.   local cell = subpixelGrid[cy][cx] or {mask=0, col=col}
  27.   cell.mask = bit32.bor(cell.mask, bit32.lshift(1, bit))
  28.   cell.col = col or cell.col
  29.   subpixelGrid[cy][cx] = cell
  30. end
  31.  
  32. local function drawLine(x1, y1, x2, y2, col)
  33.   x1 = math.floor(x1 * 2 + 0.5)
  34.   y1 = math.floor(y1 * 2 + 0.5)
  35.   x2 = math.floor(x2 * 2 + 0.5)
  36.   y2 = math.floor(y2 * 2 + 0.5)
  37.   local dx = math.abs(x2 - x1)
  38.   local dy = math.abs(y2 - y1)
  39.   local sx = x1 < x2 and 1 or -1
  40.   local sy = y1 < y2 and 1 or -1
  41.   local err = dx - dy
  42.   while true do
  43.     setSubpixel(x1, y1, col)
  44.     if x1 == x2 and y1 == y2 then break end
  45.     local e2 = 2 * err
  46.     if e2 > -dy then err = err - dy; x1 = x1 + sx end
  47.     if e2 <  dx then err = err + dx; y1 = y1 + sy end
  48.   end
  49. end
  50.  
  51. local function renderSubpixels(mon)
  52.   for y, row in pairs(subpixelGrid) do
  53.     for x, cell in pairs(row) do
  54.       mon.setCursorPos(x, y)
  55.       mon.setTextColor(cell.col or colors.white)
  56.       mon.write(string.char(128 + (cell.mask or 0)))
  57.     end
  58.   end
  59.   mon.setTextColor(colors.white)
  60. end
  61.  
  62. local function clearSubpixels()
  63.   subpixelGrid = {}
  64. end
  65.  
  66. local function addTimestamp()
  67.   if not feHistory.timestamps then feHistory.timestamps = {} end
  68.   local t = textutils.formatTime(os.time(), true)
  69.   table.insert(feHistory.timestamps, t)
  70.   if #feHistory.timestamps > HISTORY_MAX then
  71.     table.remove(feHistory.timestamps, 1)
  72.   end
  73. end
  74.  
  75. local function drawBackground()
  76.   mon.setBackgroundColor(colors.black)
  77.   mon.clear()
  78.   clearSubpixels()
  79.  
  80.   local w, h = mon.getSize()
  81.   local left, top     = 3, 3
  82.   local right, bottom = w - 2, h - 2
  83.   local graph_w       = right - left
  84.   local graph_h       = bottom - top
  85.  
  86.   if #feHistory == 0 then
  87.     return left, top, right, bottom, graph_w, graph_h, 0, 1, 1
  88.   end
  89.  
  90.   -- compute FE history min/max including zero
  91.   local minV, maxV = feHistory[1], feHistory[1]
  92.   for i = 2, #feHistory do
  93.     local v = feHistory[i]
  94.     if v < minV then minV = v end
  95.     if v > maxV then maxV = v end
  96.   end
  97.   minV = math.min(minV, 0)
  98.   maxV = math.max(maxV, 0)
  99.   local range = maxV - minV
  100.   if range == 0 then range = 1 end  -- **SAFEGUARD for zero range**
  101.  
  102.   -- draw horizontal zero line as subpixels (use gray)
  103.   local zero_t = (0 - minV) / range
  104.   local zero_y = bottom - math.floor(zero_t * graph_h + 0.5)
  105.   drawLine(left, zero_y, right, zero_y, colors.gray)
  106.  
  107.   -- draw fixed vertical grey bars and "seconds ago" labels at the bottom
  108.   local divisions = 10 -- number of spaces, so 10 bars (adjust as you want)
  109.   local labelRow = bottom + 1
  110.   local stepDiv = graph_w / divisions
  111.   for d = 0, divisions do
  112.     local x = left + math.floor(d * stepDiv + 0.5)
  113.     drawLine(x, top, x, bottom, colors.gray)
  114.     -- offset label in seconds ago
  115.     local secondsAgo = (divisions - d) * INTERVAL * math.floor(HISTORY_MAX / divisions)
  116.     mon.setCursorPos(x-2, labelRow)
  117.     mon.setTextColor(colors.lightGray)
  118.     mon.write("-" .. tostring(secondsAgo))
  119.     mon.setTextColor(colors.white)
  120.   end
  121.  
  122.   return left, top, right, bottom, graph_w, graph_h, minV, maxV, range
  123. end
  124.  
  125. local function drawPctGraph(left, top, right, bottom, gw, gh)
  126.   if #pctHistory == 0 then return end
  127.   local step   = gw / (HISTORY_MAX - 1)
  128.   local offset = HISTORY_MAX - #pctHistory
  129.  
  130.   local px, py
  131.   for i, p in ipairs(pctHistory) do
  132.     local x = left + math.floor((offset + i - 1) * step + 0.5)
  133.     local y = bottom - math.floor((p / 100) * gh + 0.5)
  134.     if px then drawLine(px, py, x, y, colors.red) end
  135.     px, py = x, y
  136.   end
  137. end
  138.  
  139. local function drawFEGraph(left, top, right, bottom, gw, gh, vmin, vmax, vrange)
  140.   if #feHistory == 0 then return end
  141.   if vrange == 0 then vrange = 1 end  -- **SAFEGUARD for zero range**
  142.   local step   = gw / (HISTORY_MAX - 1)
  143.   local offset = HISTORY_MAX - #feHistory
  144.  
  145.   local px, py
  146.   for i, v in ipairs(feHistory) do
  147.     local x = left + math.floor((offset + i - 1) * step + 0.5)
  148.     local t = (v - vmin) / vrange
  149.     local y = bottom - math.floor(t * gh + 0.5)
  150.     if px then drawLine(px, py, x, y, colors.lime) end
  151.     px, py = x, y
  152.   end
  153. end
  154.  
  155. local function drawPctLabels(left, bottom, gw, gh, offset)
  156.   if #pctHistory == 0 then return end
  157.   local minIdx, maxIdx = 1, 1
  158.   for i = 2, #pctHistory do
  159.     if pctHistory[i] < pctHistory[minIdx] then minIdx = i end
  160.     if pctHistory[i] > pctHistory[maxIdx] then maxIdx = i end
  161.   end
  162.   local currIdx = #pctHistory
  163.   local function labelPoint(idx)
  164.     local p = pctHistory[idx]
  165.     local x = left + math.floor((offset + idx - 1) * gw / (HISTORY_MAX - 1) + 0.5)
  166.     local y = bottom - math.floor((p / 100) * gh + 0.5)
  167.     local lbl = string.format("%.1f%%", p)
  168.     mon.setBackgroundColor(colors.black)
  169.     mon.setTextColor(colors.white)
  170.     mon.setCursorPos(x - math.floor(#lbl/2), y - 1)
  171.     mon.write(lbl)
  172.   end
  173.   labelPoint(minIdx)
  174.   labelPoint(maxIdx)
  175.   labelPoint(currIdx)
  176. end
  177.  
  178. local function drawFELabels(left, bottom, gw, gh, offset, vmin, vrange)
  179.   if #feHistory == 0 then return end
  180.   if vrange == 0 then vrange = 1 end  -- **SAFEGUARD for zero range**
  181.   local minIdx, maxIdx = 1, 1
  182.   for i = 2, #feHistory do
  183.     if feHistory[i] < feHistory[minIdx] then minIdx = i end
  184.     if feHistory[i] > feHistory[maxIdx] then maxIdx = i end
  185.   end
  186.   local currIdx = #feHistory
  187.   local function labelPoint(idx)
  188.     local v = feHistory[idx]
  189.     local x = left + math.floor((offset + idx - 1) * gw / (HISTORY_MAX - 1) + 0.5)
  190.     local t = (v - vmin) / vrange
  191.     local y = bottom - math.floor(t * gh + 0.5)
  192.     local lbl = string.format("%d", math.floor(v + 0.5))
  193.     mon.setBackgroundColor(colors.black)
  194.     mon.setTextColor(colors.white)
  195.     mon.setCursorPos(x - math.floor(#lbl/2), y - 1)
  196.     mon.write(lbl)
  197.   end
  198.   labelPoint(minIdx)
  199.   labelPoint(maxIdx)
  200.   labelPoint(currIdx)
  201. end
  202.  
  203. -- Initialize display
  204. mon.setTextScale(0.5)
  205. mon.setBackgroundColor(colors.black)
  206. mon.clear()
  207.  
  208. -- Main loop: sample at INTERVAL, redraw both graphs
  209. local prevEnergy = nil
  210. local timer = os.startTimer(INTERVAL)
  211. while true do
  212.   local ev, id = os.pullEvent()
  213.   if ev == "timer" and id == timer then
  214.     local _, data = rednet.receive("generator_status")
  215.     if data then
  216.       local energy = data.totalEnergy or 0
  217.       local pct    = data.accPct or 0
  218.  
  219.       -- Calculate FE/t from energy delta
  220.       local fe
  221.       if prevEnergy ~= nil then
  222.         fe = (energy - prevEnergy) / (INTERVAL * 20)
  223.       else
  224.         fe = 0
  225.       end
  226.       prevEnergy = energy
  227.  
  228.       -- update histories
  229.       table.insert(feHistory,  fe)
  230.       table.insert(pctHistory, pct)
  231.       addTimestamp()
  232.       if #feHistory  > HISTORY_MAX then table.remove(feHistory, 1)  end
  233.       if #pctHistory > HISTORY_MAX then table.remove(pctHistory, 1) end
  234.  
  235.       -- draw background and get layout
  236.       local left, top, right, bottom, gw, gh, vmin, vmax, vrange =
  237.         drawBackground()
  238.       drawPctGraph( left, top, right, bottom, gw, gh )
  239.       drawFEGraph( left, top, right, bottom, gw, gh, vmin, vmax, vrange )
  240.       renderSubpixels(mon)
  241.       local offsetPct = HISTORY_MAX - #pctHistory
  242.       drawPctLabels(left, bottom, gw, gh, offsetPct)
  243.       local offsetFE = HISTORY_MAX - #feHistory
  244.       drawFELabels(left, bottom, gw, gh, offsetFE, vmin, vrange)
  245.     end
  246.     timer = os.startTimer(INTERVAL)
  247.   end
  248. end
  249.  
Advertisement
Add Comment
Please, Sign In to add comment