cyber_Ahn

Ficsit-Networks-Power_Monitor by Rostriano

Mar 7th, 2026
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 27.24 KB | None | 0 0
  1. --- PowerMonitor
  2. ---
  3. --- Created by Rostriano
  4. --- Date: 2024-08-08
  5. ---
  6.  
  7. --------------------------------------------------------------------------------
  8. -- Utility functions
  9. --------------------------------------------------------------------------------
  10.  
  11. -- Adds the contents of t2 to t1
  12. function tableConcat( t1, t2 )
  13.     for i=1, #t2 do
  14.        t1[#t1+1] = t2[i]
  15.     end
  16.     return t1
  17. end
  18.  
  19. ---Can the given value be found in a table of { key, values } ?
  20. ---@param t table
  21. ---@param value any
  22. ---@return boolean
  23. function tableHasValue( t, value )
  24.     if t == nil or value == nil then
  25.         return false
  26.     end
  27.  
  28.     for _,v in pairs( t ) do
  29.         if v == value then
  30.             return true
  31.         end
  32.     end
  33.  
  34.     return false
  35. end
  36.  
  37. ---Find and return a table of all the NetworkComponent proxies that are of the given class[es]
  38. ---@param class any Class name or table (of tables) of class names
  39. ---@param boolean Return only one
  40. ---@return table | nil | proxy: indexed table of all NetworkComponents found
  41. function getComponentsByClass( class, getOne )
  42.     local results = {}
  43.  
  44.     if ( getOne == nil ) then
  45.         getOne = false
  46.     end
  47.  
  48.     if type( class ) == "table" then
  49.  
  50.         for _, c in pairs( class ) do
  51.             local proxies = getComponentsByClass( c, getOne )
  52.             if not getOne then
  53.                 tableConcat( results, proxies )
  54.             else
  55.                 if( proxies ~= nil ) then
  56.                     return proxies
  57.                 end
  58.             end
  59.         end
  60.  
  61.     elseif type( class ) == "string" then
  62.  
  63.         local ctype = classes[ class ]
  64.         if ctype ~= nil then
  65.             local comps = component.findComponent( ctype )
  66.             for _, c in pairs( comps ) do
  67.                 local proxy = component.proxy( c )
  68.                 if getOne and proxy ~= nil then
  69.                     return proxy
  70.                 elseif not tableHasValue( results, proxy ) then
  71.                     table.insert( results, proxy )
  72.                 end
  73.             end
  74.         end
  75.  
  76.     end
  77.  
  78.     if ( getOne ) then
  79.         return {}
  80.     end
  81.  
  82.     return results
  83. end
  84.  
  85. ---Find and return a table of all the NetworkComponent proxies that are of the given class[es] and contain the given nick parts
  86. ---@param class any Class name or table (of tables) of class names
  87. ---@param class nickParts Nick or parts of a nick that we want to see
  88. ---@return table: indexed table of all NetworkComponents found
  89. function getComponentsByClassAndNick( class, nickParts )
  90.     if type( nickParts ) == 'string' then
  91.         nickParts = { nickParts }
  92.     end
  93.  
  94.     local classComponents = getComponentsByClass( class )
  95.     local results = {}
  96.  
  97.     for _, component in pairs( classComponents ) do
  98.         for _, nickPart in pairs( nickParts ) do
  99.             if component.nick:find( nickPart, 1, true ) == nil then
  100.                 goto nextComponent
  101.             end
  102.         end
  103.  
  104.         table.insert( results, component )
  105.  
  106.         ::nextComponent::
  107.     end
  108.  
  109.     return results
  110. end
  111.  
  112. ---Decodes a table of settings stored in a string.
  113. ---Settings should be encoded as: key1="value1" key2="value2" ...
  114. ---@param str string Tokenized string to decode
  115. ---@param lowerKeys? boolean Force the keys to be lowercase, otherwise any cAmElCaSInG is preserved and it will be usercode responsibility to deal with it
  116. ---@return table table {key=field,value=value*} *value will not be quoted despite the requirement to enclose the value in double quotes in the string
  117. function settingsFromString( str, lowerKeys )
  118.     lowerKeys = lowerKeys or false
  119.     local results = {}
  120.     if str == nil or type( str ) ~= "string" then return results end
  121.     for key, value in string.gmatch( str, '(%w+)=(%b"")' ) do
  122.         if lowerKeys then key = string.lower( key ) end
  123.         results[ key ] = string.sub( value, 2, string.len( value ) - 1 )
  124.     end
  125.     return results
  126. end
  127.  
  128. ---Reads the table of settings stored in a Network Components nickname.  This function does not apply the setings, this merely reads the string and turns it into a {key, value} table.
  129. ---Settings should be encoded as: key1="value1" key2="value2" ...
  130. ---@param proxy userdata NetworkComponent proxy
  131. ---@param lowerKeys? boolean Force the keys to be lowercase, otherwise any cAmElCaSInG is preserved and it will be usercode responsibility to deal with it
  132. ---@return table table {key=field,value=value*} *value will not be quoted despite the requirement to enclose the value in double quotes in the nick
  133. function settingsFromComponentNickname( proxy, lowerKeys )
  134.     if proxy == nil then return nil end
  135.     return settingsFromString( proxy[ "nick" ], lowerKeys )
  136. end
  137.  
  138.  
  139. --------------------------------------------------------------------------------
  140. -- Color
  141. --------------------------------------------------------------------------------
  142. Color = {
  143.     r = 0.0,
  144.     g = 0.0,
  145.     b = 0.0,
  146.     a = 0.0,
  147.     pattern = '{r=%1.6f,g=%1.6f,b=%1.6f,a=%1.6f}',
  148. }
  149. Color.__index = Color
  150.  
  151. ---Create a new Color and return it or nil on invalid input
  152. ---@param r number
  153. ---@param g number
  154. ---@param b number
  155. ---@param a number
  156. ---@return Color
  157. function Color.new( r, g, b, a )
  158.     if r == nil or type( r ) ~= "number" then return nil end
  159.     if g == nil or type( g ) ~= "number" then return nil end
  160.     if b == nil or type( b ) ~= "number" then return nil end
  161.     if a == nil or type( a ) ~= "number" then return nil end
  162.     local o = {
  163.         r = r,
  164.         g = g,
  165.         b = b,
  166.         a = a,
  167.     }
  168.     setmetatable( o, { __index = Color } )
  169.     return o
  170. end
  171.  
  172. Color.BLACK             = Color.new( 0.000, 0.000, 0.000, 1.0 )
  173. Color.WHITE             = Color.new( 1.000, 1.000, 1.000, 1.0 )
  174. Color.GREY_0750         = Color.new( 0.750, 0.750, 0.750, 1.0 )
  175. Color.GREY_0500         = Color.new( 0.500, 0.500, 0.500, 1.0 )
  176. Color.GREY_0250         = Color.new( 0.250, 0.250, 0.250, 1.0 )
  177. Color.GREY_0125         = Color.new( 0.125, 0.125, 0.125, 1.0 )
  178.  
  179. Color.RED               = Color.new( 1.000, 0.000, 0.000, 1.0 )
  180. Color.GREEN             = Color.new( 0.000, 1.000, 0.000, 1.0 )
  181. Color.GREEN_0750        = Color.new( 0.000, 0.750, 0.000, 1.0 )
  182. Color.GREEN_0500        = Color.new( 0.000, 0.500, 0.000, 1.0 )
  183. Color.BLUE              = Color.new( 0.000, 0.000, 1.000, 1.0 )
  184.  
  185. Color.FICSIT_ORANGE     = Color.new( 1.000, 0.550, 0.200, 1.0 )
  186.  
  187.  
  188. --------------------------------------------------------------------------------
  189. -- Vector 2d
  190. --------------------------------------------------------------------------------
  191. Vector2d = {
  192.     x = 0,
  193.     y = 0,
  194.     pattern = '{x=%d,y=%d}',
  195. }
  196. Vector2d.__index = Vector2d
  197.  
  198. ---Create a new Vector2d and return it
  199. ---@param x integer
  200. ---@param y integer
  201. ---@return Vector2d
  202. function Vector2d.new( x, y )
  203.     if x == nil or type( x ) ~= "number" then return nil end
  204.     if y == nil or type( y ) ~= "number" then return nil end
  205.     local o = { x = math.floor( x ), y = math.floor( y ) }
  206.     setmetatable( o, { __index = Vector2d } )
  207.     return o
  208. end
  209.  
  210.  
  211. --------------------------------------------------------------------------------
  212. -- SizeLimitedList
  213. --------------------------------------------------------------------------------
  214. SizeLimitedList = {}
  215. SizeLimitedList.__index = SizeLimitedList
  216.  
  217. function SizeLimitedList.new( maxSize )
  218.   local self = setmetatable( {
  219.     first = 0,
  220.     maxSize = 10,
  221.     currSize = 0,
  222.     items = {},
  223.  
  224.     maxVal = nil,
  225.     minVal = nil,
  226.  
  227.  
  228.   }, SizeLimitedList )
  229.   if maxSize ~= nil then
  230.       self.maxSize = maxSize
  231.   end
  232.  
  233.   return self
  234. end
  235.  
  236. function SizeLimitedList:setSize( newSize )
  237.     if self.currSize > newSize then
  238.         local shrinkBy = self.currSize - newSize
  239.         local newFirst = self.first + shrinkBy
  240.  
  241.         while self.first < newFirst do
  242.             self.items[ self.first ] = nil
  243.             self.first = self.first + 1
  244.         end
  245.     end
  246.  
  247.     self.currSize = math.min( self.currSize, newSize )
  248.     self.maxSize = newSize
  249. end
  250.  
  251. function SizeLimitedList:getSize()
  252.     return self.currSize
  253. end
  254.  
  255. function SizeLimitedList:getMaxSize()
  256.     return self.maxSize
  257. end
  258.  
  259. function SizeLimitedList:add( item )
  260.     self.items[ self.first + self.currSize ] = item
  261.  
  262.     if self.currSize < self.maxSize then
  263.         self.currSize = self.currSize + 1
  264.     else
  265.         self.items[ self.first ] = nil
  266.         self.first = self.first + 1
  267.     end
  268.  
  269.     self:updateMinMaxVals()
  270. end
  271.  
  272. function SizeLimitedList:getMinVal( default )
  273.     return self.minVal or default
  274. end
  275.  
  276. function SizeLimitedList:getMaxVal( default )
  277.     return self.maxVal or default
  278. end
  279.  
  280. function SizeLimitedList:iterate( f )
  281.     local index = self.first
  282.     local sentinel = self.first + self.currSize
  283.  
  284.     while index < sentinel do
  285.         f( self.items[ index ] )
  286.         index = index + 1
  287.     end
  288. end
  289.  
  290. function SizeLimitedList:updateMinMaxVals()
  291.     self.minVal = nil
  292.     self.maxVal = nil
  293.  
  294.     self:iterate(
  295.         function( currVal )
  296.             if self.minVal == nil then
  297.                 self.minVal = currVal
  298.             else
  299.                 self.minVal = math.min( self.minVal, currVal )
  300.             end
  301.  
  302.             if self.maxVal == nil then
  303.                 self.maxVal = currVal
  304.             else
  305.                 self.maxVal = math.max( self.maxVal, currVal )
  306.             end
  307.         end
  308.     )
  309. end
  310.  
  311.  
  312. --------------------------------------------------------------------------------
  313. -- ScreenElement
  314. --------------------------------------------------------------------------------
  315. --[[
  316.      Offers the regular GPU T2 drawing commands, translating
  317.      the coordinates to a screen location.
  318.  
  319.      This serves as the base for a graphics library, allowing
  320.      multiple elements to be added that will (re)draw when
  321.      this Element is being (re)drawn
  322. ]]
  323. ScreenElement = {
  324.     gpu = nil,
  325.     position = nil,
  326.     dimensions = nil,
  327.     subElements = {},
  328.  
  329.     -- Helper functions at the bottom of this script
  330.     reposition = nil,
  331. }
  332.  
  333. function ScreenElement:new( o )
  334.     o = o or {}
  335.     self.__index = self
  336.     setmetatable( o, self )
  337.  
  338.     return o
  339. end
  340.  
  341. function ScreenElement:init( gpu, position, dimensions )
  342.     self.gpu = gpu
  343.     self.position = position
  344.     self.dimensions = dimensions
  345. end
  346.  
  347. function ScreenElement:addElement( e )
  348.     if e == nil then
  349.         return
  350.     end
  351.  
  352.     table.insert( self.subElements, e )
  353. end
  354.  
  355. -- Draw the element and all sub elements that have been added
  356. function ScreenElement:draw()
  357.     print( "Repainting" )
  358.     for _, element in pairs(self.subElements) do
  359.         element:draw()
  360.     end
  361. end
  362.  
  363. function ScreenElement:flush()
  364.     computer.error( 'ScreenElement:flush() should not be called; call draw(), then do a gpu:flush()' )
  365. end
  366.  
  367. function ScreenElement:measureText( Text, Size, bMonospace )
  368.     return self.gpu:measureText( Text, Size, bMonospace )
  369. end
  370.  
  371. --- Draws some Text at the given position (top left corner of the text), text, size, color and rotation.
  372. ---@param position Vector2D @The position of the top left corner of the text.
  373. ---@param text string @The text to draw.
  374. ---@param size number @The font size used.
  375. ---@param color Color @The color of the text.
  376. ---@param monospace boolean @True if a monospace font should be used.
  377. function ScreenElement:drawText( position, text, size, color, monospace )
  378.     self.gpu:drawText(
  379.         self:reposition( position ),
  380.         text,
  381.         size,
  382.         color,
  383.         monospace
  384.     )
  385. end
  386.  
  387. --- Draws a Rectangle with the upper left corner at the given local position, size, color and rotation around the upper left corner.
  388. ---@param position Vector2D @The local position of the upper left corner of the rectangle.
  389. ---@param size Vector2D @The size of the rectangle.
  390. ---@param color Color @The color of the rectangle.
  391. ---@param image string @If not empty string, should be image reference that should be placed inside the rectangle.
  392. ---@param rotation number @The rotation of the rectangle around the upper left corner in degrees.
  393. function ScreenElement:drawRect( position, size, color, image, rotation )
  394.     self.gpu:drawRect(
  395.         self:reposition( position ),
  396.         size,
  397.         color,
  398.         image,
  399.         rotation
  400.     )
  401. end
  402.  
  403. --- Draws connected lines through all given points with the given thickness and color.
  404. ---@param points Vector2D[] @The local points that get connected by lines one after the other.
  405. ---@param thickness number @The thickness of the lines.
  406. ---@param color Color @The color of the lines.
  407. function ScreenElement:drawLines( points, thickness, color )
  408.     if #points < 2 then
  409.        return
  410.     end
  411.  
  412.     local newPoints = {}
  413.  
  414.     for _, currPoint in pairs( points ) do
  415.         table.insert( newPoints, self:reposition( currPoint ) )
  416.     end
  417.  
  418.     self.gpu:drawLines(
  419.         newPoints,
  420.         thickness,
  421.         color
  422.     )
  423. end
  424.  
  425. function ScreenElement:reposition( vector )
  426.     return Vector2d.new(
  427.         self.position.x + vector.x,
  428.         self.position.y + vector.y
  429.     )
  430. end
  431.  
  432.  
  433. --------------------------------------------------------------------------------
  434. -- Plotter
  435. --------------------------------------------------------------------------------
  436. Plotter = ScreenElement:new()
  437.  
  438. Plotter.__index = Plotter
  439. Plotter.graph = nil -- The graph this plotter belongs to
  440. Plotter.maxVal = nil
  441. Plotter.scaleFactorX = nil
  442. Plotter.color = Color.GREY_0500
  443. Plotter.lineThickness = 10
  444. Plotter.dataSource = {}
  445.  
  446. function Plotter.new( o )
  447.     local plotter = setmetatable( o or {}, Plotter )
  448.  
  449.     if o ~= nil and rawget( o, 'dataSource' ) ~= nil then
  450.         plotter:setDataSource( o.dataSource )
  451.     end
  452.  
  453.     return plotter
  454. end
  455.  
  456. function Plotter:setDataSource( dataSource )
  457.     self.dataSource = dataSource
  458.     self.scaleFactorX = self.graph.dimensions.x / ( self.dataSource:getMaxSize() - 1 )
  459. end
  460.  
  461. function Plotter:setColor( color )
  462.     self.color = color
  463. end
  464.  
  465. function Plotter:setLineThickness( lineThickness )
  466.     self.lineThickness = lineThickness
  467. end
  468.  
  469. function Plotter:draw()
  470.     local i = 0
  471.     local points = {}
  472.     self.dataSource:iterate(
  473.         function( currVal )
  474.             local xPos = ( i + self.dataSource.maxSize - self.dataSource.currSize ) * self.scaleFactorX
  475.             local yPos = self.graph.dimensions.y - currVal * self.graph.scaleFactorY
  476.  
  477.             local position = Vector2d.new( xPos, yPos )
  478.             table.insert( points, position )
  479.             i = i + 1
  480.         end
  481.     )
  482.  
  483.     self:drawLines( points, self.lineThickness, self.color )
  484. end
  485.  
  486.  
  487. --------------------------------------------------------------------------------
  488. -- Graph
  489. --------------------------------------------------------------------------------
  490. Graph = ScreenElement:new()
  491.  
  492. Graph.__index = Graph
  493. Graph.scaleFactorY = nil
  494. Graph.maxVal = nil
  495. Graph.dimensions = nil
  496. Graph.scaleMarginFactor = 0.2
  497. Graph.dataSources = {}
  498. Graph.plotters = {}
  499.  
  500. function Graph.new()
  501.     return setmetatable( {}, Graph )
  502. end
  503.  
  504. function Graph:addPlotter( name, config )
  505.     local plotter = Plotter.new()
  506.     self.plotters[ name ] = plotter
  507.  
  508.     if config ~= nil then
  509.         self:configurePlotter( name, config )
  510.     end
  511. end
  512.  
  513. function Graph:configurePlotter( name, config )
  514.     local plotter = self.plotters[ name ]
  515.     for k, v in pairs( config ) do
  516.         plotter[ k ] = v
  517.     end
  518.     plotter.graph = self
  519.  
  520.     if rawget( plotter, 'dataSource' ) ~= nil then
  521.         plotter:setDataSource( config.dataSource )
  522.         table.insert( self.dataSources, config.dataSource or {} )
  523.     end
  524. end
  525.  
  526. function Graph:setMaxVal( maxVal )
  527.     self.maxVal = maxVal
  528.     self.scaleFactorY = self.dimensions.y / maxVal
  529. end
  530.  
  531. function Graph:setDimensions( dimensions )
  532.     self.dimensions = dimensions
  533.  
  534.     for _,currItem in ipairs( self.dataSources ) do
  535.         currItem.dimensions = dimensions
  536.     end
  537. end
  538.  
  539. function Graph:draw()
  540.     if self.maxVal == nil then
  541.         self:autoResize()
  542.     end
  543.  
  544.     for _, plotter in pairs( self.plotters ) do
  545.         plotter:draw()
  546.     end
  547. end
  548.  
  549. function Graph:autoResize()
  550.     local maxVal = self:getMaxVal()
  551.  
  552.     if self.scaleFactorY == nil then
  553.         return self:initScaleFactors( maxVal )
  554.     end
  555.  
  556.     local maxDisplayableVal = self.dimensions.y / ( self.scaleFactorY or 0.00000000001 )
  557.     if
  558.         maxDisplayableVal < maxVal or
  559.         maxDisplayableVal * self.scaleMarginFactor > maxVal
  560.     then
  561.         self:initScaleFactors( maxVal )
  562.     end
  563. end
  564.  
  565. function Graph:initScaleFactors( maxVal )
  566.     if maxVal == nil then
  567.         maxVal = 0.00000000001
  568.     end
  569.  
  570.     self.scaleFactorY = self.dimensions.y / ( maxVal * ( 1 + self.scaleMarginFactor ) )
  571. end
  572.  
  573. function Graph:getMaxVal()
  574.     local maxVal = nil
  575.     for _,currItem in ipairs( self.dataSources ) do
  576.         maxVal = math.max( maxVal or currItem:getMaxVal(), currItem:getMaxVal() )
  577.     end
  578.     return maxVal
  579. end
  580.  
  581.  
  582. --------------------------------------------------------------------------------
  583. -- Footer
  584. --------------------------------------------------------------------------------
  585. Footer = ScreenElement:new()
  586. Footer.fontSize = 50
  587. Footer.textColor = Color.GREY_0750
  588. Footer.textVerticalOffset = -22
  589.  
  590. function Footer:draw()
  591.     self:drawRect( Vector2d.new( 100,50 ), Vector2d.new( 50,50 ), self.colors.consumption, nil, nil )
  592.     self:drawText( Vector2d.new( 200,50+self.textVerticalOffset ), self._getLabel( "Consumption", self.values.consumption), self.fontSize, self.textColor)
  593.  
  594.     self:drawRect( Vector2d.new( 100,150 ), Vector2d.new( 50,50 ), self.colors.production, nil, nil )
  595.     self:drawText( Vector2d.new( 200,150+self.textVerticalOffset ), self._getLabel( "Production", self.values.production), self.fontSize, self.textColor)
  596.  
  597.     self:drawRect( Vector2d.new( 1100,50 ), Vector2d.new( 50,50 ), self.colors.maxConsumption, nil, nil )
  598.     self:drawText( Vector2d.new( 1200,50+self.textVerticalOffset ), self._getLabel( "Max. consumption", self.values.maxPowerConsumption), self.fontSize, self.textColor)
  599.  
  600.     self:drawRect( Vector2d.new( 1100,150 ), Vector2d.new( 50,50 ), self.colors.capacity, nil, nil )
  601.     self:drawText( Vector2d.new( 1200,150+self.textVerticalOffset ), self._getLabel( "Production capacity", self.values.capacity), self.fontSize, self.textColor)
  602. end
  603.  
  604. function Footer:setValues(values)
  605.     self.values = values
  606. end
  607.  
  608. function Footer._getLabel( text, value )
  609.     if value == nil then
  610.         value = 'NaN'
  611.     else
  612.         value = string.format( '%.1f', value )
  613.     end
  614.  
  615.     return text .. ' ' .. value .. ' MW'
  616. end
  617.  
  618.  
  619. --------------------------------------------------------------------------------
  620. -- BatteryInfo
  621. --------------------------------------------------------------------------------
  622. BatteryInfo = ScreenElement:new()
  623. BatteryInfo.line = 0
  624. BatteryInfo.lineHeight = 60
  625. BatteryInfo.fontSize = 35
  626. BatteryInfo.textColor = Color.GREY_0750
  627. BatteryInfo.dataFontSize = 50
  628. BatteryInfo.dataColor = Color.WHITE
  629. BatteryInfo.colorBad = Color.RED
  630. BatteryInfo.colorGood = Color.new( 0.000, 1.000, 0.000, 1.0 )
  631.  
  632. function BatteryInfo:draw()
  633.     -- We get the circuit inside the loop so that stuff doesn't crash whenever a wire is detached
  634.     local circuit = self.connector:getCircuit()
  635.  
  636.     if not circuit.hasBatteries then
  637.         return
  638.     end
  639.  
  640.     local batteryCapacity = ( circuit and circuit.batteryCapacity ) or 0
  641.     local batteryStore = ( circuit and circuit.batteryStore ) or 0
  642.     local batteryStorePercent = ( circuit and 100 * circuit.batteryStorePercent ) or 0
  643.     local batteryTimeUntilFull = ( circuit and circuit.batteryTimeUntilFull ) or 0
  644.     local batteryTimeUntilEmpty = ( circuit and circuit.batteryTimeUntilEmpty ) or 0
  645.     local batteryIn = ( circuit and circuit.batteryIn ) or 0
  646.     local batteryOut = ( circuit and circuit.batteryOut ) or 0
  647.  
  648.     self.line = 0
  649.  
  650.     self:print( 'Stored' )
  651.     self:print( string.format( '%.1f %%', batteryStorePercent ), self.dataFontSize, self.percentageColor( batteryStorePercent ) )
  652.     self.line = self.line + 2
  653.  
  654.     self:print( 'Charge' )
  655.     if( batteryStorePercent == 100 ) then
  656.         self:print( string.format( '%.1f MWh', batteryStore ), self.dataFontSize, self.percentageColor( batteryStorePercent ) )
  657.     else
  658.         self:print( string.format( '%.1f / %.1f MWh', batteryStore, batteryCapacity ), self.dataFontSize, self.percentageColor( batteryStorePercent ) )
  659.     end
  660.     self.line = self.line + 2
  661.  
  662.     if batteryOut > 0 then
  663.         self:print( 'Discharge rate' )
  664.         self:print( string.format( '%.1f MW', batteryOut ), self.dataFontSize, self.colorBad )
  665.         self.line = self.line + 2
  666.     elseif batteryIn > 0 then
  667.         self:print( 'Charge rate' )
  668.         self:print( string.format( '%.1f MW', batteryIn ), self.dataFontSize, self.colorGood )
  669.         self.line = self.line + 2
  670.     end
  671.  
  672.     if batteryTimeUntilEmpty > 0 then
  673.         self:print( 'Time until empty ' )
  674.         self:print( self.formatTime( batteryTimeUntilEmpty ), self.dataFontSize, self.colorBad )
  675.         self.line = self.line + 2
  676.     elseif batteryTimeUntilFull > 0 then
  677.         self:print( 'Time until full' )
  678.         self:print( self.formatTime( batteryTimeUntilFull ), self.dataFontSize, self.colorGood )
  679.         self.line = self.line + 2
  680.     end
  681. end
  682.  
  683. function BatteryInfo:print( text, size, color )
  684.     self.line = self.line + 1
  685.  
  686.     local yPos = self.line * self.lineHeight
  687.  
  688.     size = size or self.fontSize
  689.     color = color or self.textColor
  690.     self:drawText( Vector2d.new( 40,yPos ), text, size, color)
  691. end
  692.  
  693. function BatteryInfo.formatTime( seconds )
  694.     return string.format(
  695.       "%02d:%02d:%02d",
  696.       math.floor( seconds / 3600 ) % 24,
  697.       math.floor( seconds / 60 ) % 60,
  698.       math.floor( seconds % 60 )
  699.     )
  700. end
  701.  
  702. function BatteryInfo.percentageColor( percentage )
  703.     if percentage < 33 then
  704.         return Color.RED
  705.     elseif percentage < 80 then
  706.         return Color.FICSIT_ORANGE
  707.     elseif percentage < 100 then
  708.         return Color.GREEN
  709.     else
  710.         return Color.GREY_0750
  711.     end
  712. end
  713.  
  714.  
  715. --------------------------------------------------------------------------------
  716. -- PowerMonitor
  717. --------------------------------------------------------------------------------
  718. PowerMonitor = {
  719.     connector = nil,
  720.     gpu = nil,
  721.     pollInterval = 1,
  722.  
  723.     productionList = nil,
  724.     consumptionList = nil,
  725.     maxPowerConsumptionList = nil,
  726.  
  727.     powerGraph = nil,
  728.     footer = nil,
  729.  
  730.     colors = {
  731.         consumption = Color.FICSIT_ORANGE,
  732.         capacity = Color.GREY_0500,
  733.         production = Color.GREY_0750,
  734.         maxConsumption = Color.new( 0.050, 0.500, 0.700, 1.0 ),
  735.     },
  736.  
  737.     graphWidth = 2300,
  738.     graphHeight = 1550,
  739. }
  740.  
  741. function PowerMonitor:init( power, gpu )
  742.     print( "\nInitialising PowerMonitor\n" )
  743.  
  744.     self.gpu = gpu
  745.     local connectors = power:getPowerConnectors()
  746.     if #connectors == 0 then
  747.         computer.panic( 'The power interface has no connectors, cannot continue' )
  748.     end
  749.     self.connector = connectors[1]
  750.  
  751.     self:initLists()
  752.  
  753.     -- Screen size is 3000x1800
  754.     self.powerGraph = Graph.new()
  755.     self.powerGraph:setDimensions( Vector2d.new( self.graphWidth,self.graphHeight ) )
  756.     self.powerGraph:addPlotter(
  757.         'consumption',
  758.         {
  759.             gpu = self.gpu,
  760.             position = Vector2d.new( 0,0 ),
  761.             color = self.colors.consumption,
  762.             dataSource = self.consumptionList,
  763.         }
  764.     )
  765.     self.powerGraph:addPlotter(
  766.         'capacity',
  767.         {
  768.             gpu = self.gpu,
  769.             position = Vector2d.new( 0,0 ),
  770.             color = self.colors.capacity,
  771.             dataSource = self.capacityList,
  772.         }
  773.     )
  774.     self.powerGraph:addPlotter(
  775.         'production',
  776.         {
  777.             gpu = self.gpu,
  778.             position = Vector2d.new( 0,0 ),
  779.             color = self.colors.production,
  780.             dataSource = self.productionList,
  781.         }
  782.     )
  783.     self.powerGraph:addPlotter(
  784.         'maxConsumption',
  785.         {
  786.             gpu = self.gpu,
  787.             position = Vector2d.new( 0,0 ),
  788.             color = self.colors.maxConsumption,
  789.             dataSource = self.maxPowerConsumptionList,
  790.         }
  791.     )
  792.  
  793.     self.batteryInfo = BatteryInfo:new({ connector = self.connector })
  794.     self.batteryInfo:init( self.gpu, Vector2d.new( self.graphWidth,0 ), Vector2d.new( 3000-self.graphWidth,1800  ) )
  795.  
  796.     self.footer = Footer:new({colors = self.colors})
  797.     self.footer:init( self.gpu, Vector2d.new( 0, self.graphHeight ), Vector2d.new( self.graphWidth, 1000 ) )
  798. end
  799.  
  800. function PowerMonitor:run()
  801.     print( "PowerMonitor running")
  802.  
  803.     while true do
  804.         self:collectData()
  805.  
  806.         -- Paint background
  807.         -- self.gpu:drawRect( Vector2d.new(0,0), Vector2d.new(3000,1800), Color.WHITE, nil, nil )
  808.  
  809.         self.gpu:drawLines(
  810.             { Vector2d.new( 0,self.graphHeight ), Vector2d.new( self.graphWidth,self.graphHeight ) },
  811.             5,
  812.             Color.GREY_0500
  813.         )
  814.         self.gpu:drawLines(
  815.             { Vector2d.new( self.graphWidth,0 ), Vector2d.new( self.graphWidth,self.graphHeight ) },
  816.             5,
  817.             Color.GREY_0500
  818.         )
  819.  
  820.         self.powerGraph:draw()
  821.  
  822.         self.footer:setValues({
  823.             production = self.production,
  824.             capacity = self.capacity,
  825.             consumption = self.consumption,
  826.             maxPowerConsumption = self.maxPowerConsumption,
  827.         })
  828.         self.footer:draw()
  829.  
  830.         self.batteryInfo:draw()
  831.  
  832.         self.gpu:flush()
  833.         event.pull( self.pollInterval )
  834.     end
  835. end
  836.  
  837. function PowerMonitor:initLists()
  838.     self.productionList = SizeLimitedList.new( 100 )
  839.     self.capacityList = SizeLimitedList.new( 100 )
  840.     self.consumptionList = SizeLimitedList.new( 100 )
  841.     self.maxPowerConsumptionList = SizeLimitedList.new( 100 )
  842. end
  843.  
  844. function PowerMonitor:collectData()
  845.     -- We get the circuit inside the loop so that stuff doesn't crash whenever a wire is detached
  846.     local circuit = self.connector:getCircuit()
  847.  
  848.     self.production = ( circuit and circuit.production ) or 0
  849.     self.capacity = ( circuit and circuit.capacity ) or 0
  850.     self.consumption = ( circuit and circuit.consumption ) or 0
  851.     self.maxPowerConsumption = ( circuit and circuit.maxPowerConsumption ) or 0
  852.  
  853.     self.productionList:add( self.production )
  854.     self.capacityList:add( self.capacity )
  855.     self.consumptionList:add( self.consumption )
  856.     self.maxPowerConsumptionList:add( self.maxPowerConsumption )
  857. end
  858.  
  859. --------------------------------------------------------------------------------
  860.  
  861. local power = getComponentsByClass( {
  862.     "FGBuildablePowerPole", -- Power poles and wall outlets
  863.     "CircuitSwitch",
  864.     "Build_PriorityPowerSwitch_C",
  865.     "PowerStorage",
  866. } )
  867. if #power == 0 then
  868.     computer.panic( "No power pole or wall outlet hooked up; nothing to monitor" )
  869. end
  870. power = power[1]
  871.  
  872. local gpu = computer.getPCIDevices( classes.GPU_T2_C )[1]
  873. if gpu == nil then
  874.     computer.panic( "No GPU T2 found. Cannot continue." )
  875. end
  876.  
  877. local computerSettings = settingsFromComponentNickname( computer.getInstance() )
  878. local screens = getComponentsByClassAndNick( {
  879.     "ModuleScreen_C",
  880.     "Build_Screen_C",
  881. }, computerSettings.screen or '' )
  882. if #screens == 0 then
  883.     computer.panic( "No screen found. Cannot continue." )
  884. end
  885.  
  886. gpu:bindScreen( screens[1] )
  887.  
  888. screenSize = gpu:getScreenSize()
  889. print( 'Screen resolution: ' .. screenSize.x .. 'x' .. screenSize.y )
  890.  
  891. PowerMonitor:init( power, gpu )
  892. PowerMonitor:run()
  893.  
Advertisement
Add Comment
Please, Sign In to add comment