diff --git a/Packages/BackupPowerSupply/README.md b/Packages/BackupPowerSupply/README.md new file mode 100644 index 0000000..051c6a9 --- /dev/null +++ b/Packages/BackupPowerSupply/README.md @@ -0,0 +1,50 @@ +# The Backup Power Supply +The Ficsit Backup Power Supply manages backup power - sort of like a UPS but too slow to +guarantee uninterrupted power. Or is it? + +## Setup +To use the Ficsit BPS, you will need one or more power storages, plus two power switches and of course a computer. +The switches must be hooked up as shown below: + + mains power load power storage + | | | + | | | + +-----/ ----+----/ -----+ + + mains power storage + switch switch + + +A demonstration setup is shown below: + +![Example setup of the Ficsit Backup Power Supply](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/BackupPowerSupply/preview1.jpg "Example setup of the Ficsit BPS") +Example setup of the Ficsit Backup Power Supply + +Please observe the following: +- the mains switch must have the following nick: "BPS mainsSwitch" +- the power storage switch must have the following nick: "BPS powerStorageSwitch" +- the computer needs to have a network connection to both switches and a power storage. + +## Operation and functioning +Once operational, the Ficsit Backup Power Supply will not need any attention from the employee. + +The Ficsit BPS will monitor the grid and the connected power storage(s). When the overall load on the grid exceeds the production, it will disconnect itself from the grid and engage the power storage(s) so you can continue operating from the power storage(s) as long as there is any charge left in the power storage(s). Then, when the grid is balanced again, it will start charging the power storage(s). + +![Normal operation](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/BackupPowerSupply/preview2.jpg "The Ficsit BPS during Normal operation") +The Ficsit BPS during Normal operation + +![Charging](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/BackupPowerSupply/preview3.jpg "The Ficsit BPS while Charging") +The Ficsit BPS while Charging + +![Providing backup power](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/BackupPowerSupply/preview4.jpg "The ficsit BPS providing backup power") +The ficsit BPS providing backup power + +## Considerations for designing your grid +To guarantee grid stability, Ficsit advises to have a minimum of one power storage connected +directly to the grid. This ensures there will be enough power available for the limited +amount of time it takes for your Backup Power Supply management system to flip its switches. + +Please note that while the charge rate of a power storage is limited to 100 MW, its +discharge rate is unlimited. This means that for the maximum of five seconds it takes +for the BPS to provide backup power, a single power storage can supply as much +as 72,000 MW of power. diff --git a/Packages/BackupPowerSupply/metadata.toml b/Packages/BackupPowerSupply/metadata.toml new file mode 100644 index 0000000..0d7e6cf --- /dev/null +++ b/Packages/BackupPowerSupply/metadata.toml @@ -0,0 +1,4 @@ +name = "Backup Power Supply" +short_description = "Manages backup power - sort of like a UPS but too slow to guarantee uninterrupted power" +tags = ["backup", "power", "UPS"] +authors = ["Rostriano"] diff --git a/Packages/BackupPowerSupply/preview1.jpg b/Packages/BackupPowerSupply/preview1.jpg new file mode 100644 index 0000000..993d039 Binary files /dev/null and b/Packages/BackupPowerSupply/preview1.jpg differ diff --git a/Packages/BackupPowerSupply/preview2.jpg b/Packages/BackupPowerSupply/preview2.jpg new file mode 100644 index 0000000..ede7e23 Binary files /dev/null and b/Packages/BackupPowerSupply/preview2.jpg differ diff --git a/Packages/BackupPowerSupply/preview3.jpg b/Packages/BackupPowerSupply/preview3.jpg new file mode 100644 index 0000000..7db60cd Binary files /dev/null and b/Packages/BackupPowerSupply/preview3.jpg differ diff --git a/Packages/BackupPowerSupply/preview4.jpg b/Packages/BackupPowerSupply/preview4.jpg new file mode 100644 index 0000000..b898a2e Binary files /dev/null and b/Packages/BackupPowerSupply/preview4.jpg differ diff --git a/Packages/BackupPowerSupply/v0.0.1/EEPROM.lua b/Packages/BackupPowerSupply/v0.0.1/EEPROM.lua new file mode 100644 index 0000000..ed355ba --- /dev/null +++ b/Packages/BackupPowerSupply/v0.0.1/EEPROM.lua @@ -0,0 +1,146 @@ +--- +--- Ficsit Backup Power Supply +--- +--- Created by Rostriano, with code by 1000101 +--- Date: 2024-07-04 +--- + +local POLL_INTERVAL = 5 +local CATEGORY = "BPS" + +local MODE = { NORMAL="1", DISCHARGING="2", CHARGING="3" } + + +---Find and return a table of all the NetworkComponent proxies that are of the given class[es] +---@param class any Class name or table (of tables) of class names +---@param boolean Return only one +---@return table | nil | proxy: indexed table of all NetworkComponents found +function getComponentsByClass( class, getOne ) + local results = {} + + if ( getOne == nil ) then + getOne = false + end + + if type( class ) == "table" then + + for _, c in pairs( class ) do + local proxies = getComponentsByClass( c, getOne ) + if not getOne then + tableConcat( results, proxies ) + else + if( proxies ~= nil ) then + return proxies + end + end + end + + elseif type( class ) == "string" then + + local ctype = classes[ class ] + if ctype ~= nil then + local comps = component.findComponent( ctype ) + for _, c in pairs( comps ) do + local proxy = component.proxy( c ) + if getOne and proxy ~= nil then + return proxy + elseif not tableHasValue( results, proxy ) then + table.insert( results, proxy ) + end + end + end + + end + + if ( getOne ) then + return {} + end + + return results +end + + +local powerStorage = getComponentsByClass( { "PowerStorage" }, true ) + or computer.panic( "Power storage not found" ) +local powerStorageSwitch = component.proxy( component.findComponent(CATEGORY .. " powerStorageSwitch" )[1] ) + or computer.panic( "Power storage switch not found" ) +local mainsSwitch = component.proxy( component.findComponent(CATEGORY .. " mainsSwitch" )[1] ) + or computer.panic( "Mains switch not found" ) +local connectors = mainsSwitch:getPowerConnectors() + or computer.panic( "Mains switch power connectors not found" ) +local circuit1, circuit2, currMode + +function hasTrippedFuse( circuits ) + for _, circuit in pairs( circuits ) do + if circuit.isFuesed then + return true + end + end + + return false +end + +function getGridSurplus( gridCircuit ) + gridProduction = ( gridCircuit and gridCircuit.production ) or 0 + gridConsumption = gridCircuit.consumption or 0 + gridSurplus = gridProduction - gridConsumption + + print( "production, consumption, surplus:", gridProduction, gridConsumption, gridSurplus ) + + return gridSurplus +end + +-- Set switches to default settings and then enter loop to figure out where we stand +powerStorageSwitch:setIsSwitchOn( false ) +mainsSwitch:setIsSwitchOn( true ) + +while( true ) do + + -- We get the circuits inside the loop so that we can see a wire being attached + circuit1, circuit2 = connectors[1]:getCircuit(), connectors[2]:getCircuit() + + -- Determine if the grid has power + if circuit1 and circuit1.production > 0 then + gridCircuit = circuit1 + else + gridCircuit = circuit2 + end + + -- Determine what to do + if gridCircuit ~= nil and gridCircuit.production > 0 then + if powerStorage.powerStore == 100 and not hasTrippedFuse( { circuit1, circuit2 } ) then + + -- We got mains power and the power storage doesn't need to charge, all is well + if currMode ~= MODE.NORMAL then + print( "Switching to normal mode" ) + powerStorageSwitch:setIsSwitchOn( false ) + mainsSwitch:setIsSwitchOn( true ) + currMode = MODE.NORMAL + end + elseif + not hasTrippedFuse( { circuit1, circuit2 } ) + and getGridSurplus( gridCircuit ) > 0 -- Make sure we won't accidentally discharge the power storage + then + -- We got mains power; charge power storage + if currMode ~= MODE.CHARGING then + print( "Charging power storage" ) + mainsSwitch:setIsSwitchOn( true ) + powerStorageSwitch:setIsSwitchOn( true ) + currMode = MODE.CHARGING + end + print( "Power storage charge", powerStorage.powerStore, "MWh" ) + end + else -- gridCircuit.production == 0 + -- Mains power down; run on power storage + if currMode ~= MODE.DISCHARGING then + print( "Running on power storage" ) + currMode = MODE.DISCHARGING + end + mainsSwitch:setIsSwitchOn( false ) + powerStorageSwitch:setIsSwitchOn( true ) + print( "Power storage charge", powerStorage.powerStore, "MWh" ) + end + + ::continue:: + event.pull( POLL_INTERVAL ) +end diff --git a/Packages/BackupPowerSupply/v0.0.1/metadata.toml b/Packages/BackupPowerSupply/v0.0.1/metadata.toml new file mode 100644 index 0000000..36fd6c3 --- /dev/null +++ b/Packages/BackupPowerSupply/v0.0.1/metadata.toml @@ -0,0 +1,6 @@ +fin_version = ">= 0.3.19" + +[[EEPROM]] +name = "EEPROM.lua" +title = "EEPROM" +description = "The main EEPROM used when installing this package. The name is not relevant." diff --git a/Packages/Clock/README.md b/Packages/Clock/README.md new file mode 100644 index 0000000..198654a --- /dev/null +++ b/Packages/Clock/README.md @@ -0,0 +1,23 @@ +# The Ficsit multi-screen and multi-timezone clock +The Ficsit multi-screen and multi-timezone clock displays the time on one or multiple screens. + +![Example setup of the Ficsit Clock](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/Clock/preview1.jpg "Example setup of the Ficsit Clock") +Example setup of the Ficsit multi-screen and multi-timezone clock + +## Setup and installation +Please consider the following when installing your clocks: +- one GPU T1 per screen is required +- your screens should optimally have the smallest height possible and one click wider than the minimum possible width +- each screen needs to have its nick configured as follows: + - clock + - tz="\" + +A complete nick would look like this: + `clock tz="UTC"` + +### Available timezones +Currently, two timezones are available; the aforementioned UTC, plus M2CT. This stands for Massage-2(A-B)b Coordinated Time. When the tz has not been set, it will default to M2CT. + +### Alternative screen nick +If you would like to give your screens a nick other than "clock", you can do so via the computer's nick: + `screen="mystring"` diff --git a/Packages/Clock/metadata.toml b/Packages/Clock/metadata.toml new file mode 100644 index 0000000..a386b33 --- /dev/null +++ b/Packages/Clock/metadata.toml @@ -0,0 +1,4 @@ +name = "Clock" +short_description = "Displays Massage-2(A-B)b Coordinated Time or UTC on one or multiple screens" +tags = ["clock", "time", "UTC", "M2CT"] +authors = ["Rostriano"] diff --git a/Packages/Clock/preview1.jpg b/Packages/Clock/preview1.jpg new file mode 100644 index 0000000..87d0e3c Binary files /dev/null and b/Packages/Clock/preview1.jpg differ diff --git a/Packages/Clock/v0.0.1/EEPROM.lua b/Packages/Clock/v0.0.1/EEPROM.lua new file mode 100644 index 0000000..4529833 --- /dev/null +++ b/Packages/Clock/v0.0.1/EEPROM.lua @@ -0,0 +1,230 @@ +--- +--- Ficsit multi-screen and multi-timezone clock +--- +--- Created by Rostriano +--- Date: 2024-09-07 +--- +--- Based on work by 1000101 and Aider +--- + +-- These dimensions are optimal for a screen that has the smallest height possible and is one click wider than the minimum possible width +SCREEN_SIZE = { x = 7, y = 1 } + +-- Adds the contents of t2 to t1 +function tableConcat( t1, t2 ) + for i=1, #t2 do + t1[#t1+1] = t2[i] + end + return t1 +end + +---Can the given value be found in a table of { key, values } ? +---@param t table +---@param value any +---@return boolean +function tableHasValue( t, value ) + if t == nil or value == nil then + return false + end + + for _,v in pairs( t ) do + if v == value then + return true + end + end + + return false +end + +---Find and return a table of all the NetworkComponent proxies that are of the given class[es] +---@param class any Class name or table (of tables) of class names +---@param boolean Return only one +---@return table | nil | proxy: indexed table of all NetworkComponents found +function getComponentsByClass( class, getOne ) + local results = {} + + if ( getOne == nil ) then + getOne = false + end + + if type( class ) == "table" then + + for _, c in pairs( class ) do + local proxies = getComponentsByClass( c, getOne ) + if not getOne then + tableConcat( results, proxies ) + else + if( proxies ~= nil ) then + return proxies + end + end + end + + elseif type( class ) == "string" then + + local ctype = classes[ class ] + if ctype ~= nil then + local comps = component.findComponent( ctype ) + for _, c in pairs( comps ) do + local proxy = component.proxy( c ) + if getOne and proxy ~= nil then + return proxy + elseif not tableHasValue( results, proxy ) then + table.insert( results, proxy ) + end + end + end + + end + + if ( getOne ) then + return {} + end + + return results +end + +---Find and return a table of all the NetworkComponent proxies that are of the given class[es] and contain the given nick parts +---@param class any Class name or table (of tables) of class names +---@param class nickParts Nick or parts of a nick that we want to see +---@return table: indexed table of all NetworkComponents found +function getComponentsByClassAndNick( class, nickParts ) + if type( nickParts ) == 'string' then + nickParts = { nickParts } + end + + local classComponents = getComponentsByClass( class ) + local results = {} + + for _, component in pairs( classComponents ) do + for _, nickPart in pairs( nickParts ) do + if component.nick:find( nickPart, 1, true ) == nil then + goto nextComponent + end + end + + table.insert( results, component ) + + ::nextComponent:: + end + + return results +end + +---Decodes a table of settings stored in a string. +---Settings should be encoded as: key1="value1" key2="value2" ... +---@param str string Tokenized string to decode +---@param lowerKeys? boolean Force the keys to be lowercase, otherwise any cAmElCaSInG is preserved and it will be usercode responsibility to deal with it +---@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 +function settingsFromString( str, lowerKeys ) + lowerKeys = lowerKeys or false + local results = {} + if str == nil or type( str ) ~= "string" then return results end + for key, value in string.gmatch( str, '(%w+)=(%b"")' ) do + if lowerKeys then key = string.lower( key ) end + results[ key ] = string.sub( value, 2, string.len( value ) - 1 ) + end + return results +end + +---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. +---Settings should be encoded as: key1="value1" key2="value2" ... +---@param proxy userdata NetworkComponent proxy +---@param lowerKeys? boolean Force the keys to be lowercase, otherwise any cAmElCaSInG is preserved and it will be usercode responsibility to deal with it +---@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 +function settingsFromComponentNickname( proxy, lowerKeys ) + if proxy == nil then return nil end + return settingsFromString( proxy[ "nick" ], lowerKeys ) +end + +function initFromConfig() + local gpus = computer.getPCIDevices( classes.GPU_T1_C ) + if gpus == nil then + error( "No GPU T1 found" ) + end + + local computerSettings = settingsFromComponentNickname( computer.getInstance() ) + local screenNick = computerSettings.screen or "clock" + local screens = getComponentsByClassAndNick( { + "ModuleScreen_C", + "Build_Screen_C", + }, screenNick ) + if #screens == 0 then + error( "No screens found with the '" .. screenNick .. "' nick" ) + end + + if #gpus < #screens then + computer.panic( 'Not enough GPUs to drive all the screens: ' .. #gpus .. ' gpus vs ' .. #screens .. ' screens' ) + end + + local screenDefinitions = {} + for i, screen in pairs( screens ) do + local timeDisplayFunction = displayM2CT + local settings = settingsFromComponentNickname( screen, true ) + if rawget( settings, 'tz' ) == 'UTC' then + timeDisplayFunction = displayUTC + end + + table.insert( screenDefinitions, { + gpu = gpus[i], + screen = screen, + display = timeDisplayFunction, + }) + end + + return screenDefinitions +end + +function initScreens( screenDefinitions ) + for _, screenDefinition in pairs( screenDefinitions ) do + screenDefinition.gpu:bindScreen( screenDefinition.screen ) + screenDefinition.gpu:setSize( SCREEN_SIZE.x, SCREEN_SIZE.y ) + screenDefinition.gpu:setForeground( 1, 1, 1, 1 ) + screenDefinition.gpu:setBackground( 0, 0, 0, 0 ) + end +end + +function clearScreens( screenDefinitions ) + for _, screenDefinition in pairs( screenDefinitions ) do + screenDefinition.gpu:setSize( SCREEN_SIZE.x, SCREEN_SIZE.y ) + screenDefinition.gpu:fill( 0, 0, SCREEN_SIZE.x, SCREEN_SIZE.y, " " ) + end +end + +function updateScreens( screenDefinitions ) + for _, screenDefinition in pairs( screenDefinitions ) do + screenDefinition:display() + end +end + +function formatTime( timestamp ) + return string.format( + "%2d:%02d", + math.floor( timestamp / 3600 ) % 24, + math.floor( timestamp / 60 ) % 60 + ) +end + +function displayM2CT( screenDefinition ) + screenDefinition.gpu:setText( 1, 0, formatTime( computer.time() ) ) + screenDefinition.gpu:flush() +end + +function displayUTC( screenDefinition ) + screenDefinition.gpu:setText( 1, 0, formatTime( computer.magicTime()) ) + screenDefinition.gpu:flush() +end + +function init() + local screenDefinitions = initFromConfig() + clearScreens( screenDefinitions ) + initScreens( screenDefinitions ) + + return screenDefinitions +end + +local screenDefinitions = init() +while true do + event.pull(0.8) + updateScreens( screenDefinitions ) +end diff --git a/Packages/Clock/v0.0.1/metadata.toml b/Packages/Clock/v0.0.1/metadata.toml new file mode 100644 index 0000000..36fd6c3 --- /dev/null +++ b/Packages/Clock/v0.0.1/metadata.toml @@ -0,0 +1,6 @@ +fin_version = ">= 0.3.19" + +[[EEPROM]] +name = "EEPROM.lua" +title = "EEPROM" +description = "The main EEPROM used when installing this package. The name is not relevant." diff --git a/Packages/ExamplePackage/README.md b/Packages/ExamplePackage/README.md index 8f09ef8..a979537 100644 --- a/Packages/ExamplePackage/README.md +++ b/Packages/ExamplePackage/README.md @@ -1,5 +1,5 @@ # Example Package -This is an example package showcasing different options available to packages. -This Package in particular is an single EEPROM Package. +This is an example package showcasing different options available to packages. +This package in particular is a single EEPROM package. -The package is also useful as a tempalte for new packages. \ No newline at end of file +The package is also useful as a template for new packages. \ No newline at end of file diff --git a/Packages/ExamplePackage/v1.0.0/metadata.toml b/Packages/ExamplePackage/v1.0.0/metadata.toml index 89d25a3..fe544fd 100644 --- a/Packages/ExamplePackage/v1.0.0/metadata.toml +++ b/Packages/ExamplePackage/v1.0.0/metadata.toml @@ -1,7 +1,7 @@ # If you only have one EEPROM File, then this config is not needed, -# but if you want to provide multiple "variations" (like a sever and client) -# you can use this to define and describe multiple eeprom files in one package. +# but if you want to provide multiple "variations" (like a server and client), +# you can use this to define and describe multiple EEPROM files in one package. [[EEPROM]] name = "EEPROM.lua" title = "EEPROM" -description = "The main EEPROM used when installing this package. The Name is not relevant." +description = "The main EEPROM used when installing this package. The name is not relevant." diff --git a/Packages/PainExample/README.adoc b/Packages/PaintExample/README.adoc similarity index 100% rename from Packages/PainExample/README.adoc rename to Packages/PaintExample/README.adoc diff --git a/Packages/PainExample/metadata.toml b/Packages/PaintExample/metadata.toml similarity index 100% rename from Packages/PainExample/metadata.toml rename to Packages/PaintExample/metadata.toml diff --git a/Packages/PainExample/v0.0.1/EEPROM.lua b/Packages/PaintExample/v0.0.1/EEPROM.lua similarity index 100% rename from Packages/PainExample/v0.0.1/EEPROM.lua rename to Packages/PaintExample/v0.0.1/EEPROM.lua diff --git a/Packages/PainExample/v0.0.1/metadata.toml b/Packages/PaintExample/v0.0.1/metadata.toml similarity index 100% rename from Packages/PainExample/v0.0.1/metadata.toml rename to Packages/PaintExample/v0.0.1/metadata.toml diff --git a/Packages/PowerMonitor/README.md b/Packages/PowerMonitor/README.md new file mode 100644 index 0000000..96b4aee --- /dev/null +++ b/Packages/PowerMonitor/README.md @@ -0,0 +1,30 @@ +# The Ficsit Power Monitor +The Ficsit Power Monitor displays a power graph and power storage charge / discharge information. + +## Setup +Hook a computer up to both a screen and a power connector. The computer must have a GPU T2. + +You may specify which screen to use by adding the screen's nick to +the computer's nick. For example, say your screen has "powerMonitor" as its +nick, then you should add the following to your computer's nick: + `screen="powerMonitor"` + +Caveat: this software was written for a screen 10 m wide and 6 m tall and may not work properly with screens of different dimensions. + +## Operation +The Ficsit Power Monitor will show a graph of power production and consumption, as well as maximum consumption and production capacity. Additionally, it will display power storage status when applicable. + +## Examples +Examples of the Ficsit Power Monitor in operation are shown below. + +![The Ficsit Power Monitor](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/PowerMonitor/preview1.jpg "The Ficsit Power Monitor") +An example of the Ficsit Power Monitor with fully charged power storages + +![The Ficsit Power Monitor while charging](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/PowerMonitor/preview2.jpg "The Ficsit Power Monitor while charging") +An example of the Ficsit Power Monitor while charging + +![The Ficsit Power Monitor while discharging](https://raw.githubusercontent.com/Panakotta00/FicsIt-Networks-Repository/main/Packages/PowerMonitor/preview3.jpg "The Ficsit Power Monitor while discharging") +An example of the Ficsit Power Monitor while discharging + +![The Ficsit Power Monitor when power storage is nearly full](preview4.jpg "The Ficsit Power Monitor when power storage is nearly full") +An example of the Ficsit Power Monitor when power storage is nearly full diff --git a/Packages/PowerMonitor/metadata.toml b/Packages/PowerMonitor/metadata.toml new file mode 100644 index 0000000..a266015 --- /dev/null +++ b/Packages/PowerMonitor/metadata.toml @@ -0,0 +1,4 @@ +name = "Power Monitor" +short_description = "Displays a power graph and power storage charge / discharge information" +tags = ["power", "graph", "monitor"] +authors = ["Rostriano"] diff --git a/Packages/PowerMonitor/preview1.jpg b/Packages/PowerMonitor/preview1.jpg new file mode 100644 index 0000000..6f53d2a Binary files /dev/null and b/Packages/PowerMonitor/preview1.jpg differ diff --git a/Packages/PowerMonitor/preview2.jpg b/Packages/PowerMonitor/preview2.jpg new file mode 100644 index 0000000..c57e707 Binary files /dev/null and b/Packages/PowerMonitor/preview2.jpg differ diff --git a/Packages/PowerMonitor/preview3.jpg b/Packages/PowerMonitor/preview3.jpg new file mode 100644 index 0000000..d1354d9 Binary files /dev/null and b/Packages/PowerMonitor/preview3.jpg differ diff --git a/Packages/PowerMonitor/preview4.jpg b/Packages/PowerMonitor/preview4.jpg new file mode 100644 index 0000000..4a39507 Binary files /dev/null and b/Packages/PowerMonitor/preview4.jpg differ diff --git a/Packages/PowerMonitor/v0.0.1/EEPROM.lua b/Packages/PowerMonitor/v0.0.1/EEPROM.lua new file mode 100644 index 0000000..8865504 --- /dev/null +++ b/Packages/PowerMonitor/v0.0.1/EEPROM.lua @@ -0,0 +1,892 @@ +--- PowerMonitor +--- +--- Created by Rostriano +--- Date: 2024-08-08 +--- + +-------------------------------------------------------------------------------- +-- Utility functions +-------------------------------------------------------------------------------- + +-- Adds the contents of t2 to t1 +function tableConcat( t1, t2 ) + for i=1, #t2 do + t1[#t1+1] = t2[i] + end + return t1 +end + +---Can the given value be found in a table of { key, values } ? +---@param t table +---@param value any +---@return boolean +function tableHasValue( t, value ) + if t == nil or value == nil then + return false + end + + for _,v in pairs( t ) do + if v == value then + return true + end + end + + return false +end + +---Find and return a table of all the NetworkComponent proxies that are of the given class[es] +---@param class any Class name or table (of tables) of class names +---@param boolean Return only one +---@return table | nil | proxy: indexed table of all NetworkComponents found +function getComponentsByClass( class, getOne ) + local results = {} + + if ( getOne == nil ) then + getOne = false + end + + if type( class ) == "table" then + + for _, c in pairs( class ) do + local proxies = getComponentsByClass( c, getOne ) + if not getOne then + tableConcat( results, proxies ) + else + if( proxies ~= nil ) then + return proxies + end + end + end + + elseif type( class ) == "string" then + + local ctype = classes[ class ] + if ctype ~= nil then + local comps = component.findComponent( ctype ) + for _, c in pairs( comps ) do + local proxy = component.proxy( c ) + if getOne and proxy ~= nil then + return proxy + elseif not tableHasValue( results, proxy ) then + table.insert( results, proxy ) + end + end + end + + end + + if ( getOne ) then + return {} + end + + return results +end + +---Find and return a table of all the NetworkComponent proxies that are of the given class[es] and contain the given nick parts +---@param class any Class name or table (of tables) of class names +---@param class nickParts Nick or parts of a nick that we want to see +---@return table: indexed table of all NetworkComponents found +function getComponentsByClassAndNick( class, nickParts ) + if type( nickParts ) == 'string' then + nickParts = { nickParts } + end + + local classComponents = getComponentsByClass( class ) + local results = {} + + for _, component in pairs( classComponents ) do + for _, nickPart in pairs( nickParts ) do + if component.nick:find( nickPart, 1, true ) == nil then + goto nextComponent + end + end + + table.insert( results, component ) + + ::nextComponent:: + end + + return results +end + +---Decodes a table of settings stored in a string. +---Settings should be encoded as: key1="value1" key2="value2" ... +---@param str string Tokenized string to decode +---@param lowerKeys? boolean Force the keys to be lowercase, otherwise any cAmElCaSInG is preserved and it will be usercode responsibility to deal with it +---@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 +function settingsFromString( str, lowerKeys ) + lowerKeys = lowerKeys or false + local results = {} + if str == nil or type( str ) ~= "string" then return results end + for key, value in string.gmatch( str, '(%w+)=(%b"")' ) do + if lowerKeys then key = string.lower( key ) end + results[ key ] = string.sub( value, 2, string.len( value ) - 1 ) + end + return results +end + +---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. +---Settings should be encoded as: key1="value1" key2="value2" ... +---@param proxy userdata NetworkComponent proxy +---@param lowerKeys? boolean Force the keys to be lowercase, otherwise any cAmElCaSInG is preserved and it will be usercode responsibility to deal with it +---@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 +function settingsFromComponentNickname( proxy, lowerKeys ) + if proxy == nil then return nil end + return settingsFromString( proxy[ "nick" ], lowerKeys ) +end + + +-------------------------------------------------------------------------------- +-- Color +-------------------------------------------------------------------------------- +Color = { + r = 0.0, + g = 0.0, + b = 0.0, + a = 0.0, + pattern = '{r=%1.6f,g=%1.6f,b=%1.6f,a=%1.6f}', +} +Color.__index = Color + +---Create a new Color and return it or nil on invalid input +---@param r number +---@param g number +---@param b number +---@param a number +---@return Color +function Color.new( r, g, b, a ) + if r == nil or type( r ) ~= "number" then return nil end + if g == nil or type( g ) ~= "number" then return nil end + if b == nil or type( b ) ~= "number" then return nil end + if a == nil or type( a ) ~= "number" then return nil end + local o = { + r = r, + g = g, + b = b, + a = a, + } + setmetatable( o, { __index = Color } ) + return o +end + +Color.BLACK = Color.new( 0.000, 0.000, 0.000, 1.0 ) +Color.WHITE = Color.new( 1.000, 1.000, 1.000, 1.0 ) +Color.GREY_0750 = Color.new( 0.750, 0.750, 0.750, 1.0 ) +Color.GREY_0500 = Color.new( 0.500, 0.500, 0.500, 1.0 ) +Color.GREY_0250 = Color.new( 0.250, 0.250, 0.250, 1.0 ) +Color.GREY_0125 = Color.new( 0.125, 0.125, 0.125, 1.0 ) + +Color.RED = Color.new( 1.000, 0.000, 0.000, 1.0 ) +Color.GREEN = Color.new( 0.000, 1.000, 0.000, 1.0 ) +Color.GREEN_0750 = Color.new( 0.000, 0.750, 0.000, 1.0 ) +Color.GREEN_0500 = Color.new( 0.000, 0.500, 0.000, 1.0 ) +Color.BLUE = Color.new( 0.000, 0.000, 1.000, 1.0 ) + +Color.FICSIT_ORANGE = Color.new( 1.000, 0.550, 0.200, 1.0 ) + + +-------------------------------------------------------------------------------- +-- Vector 2d +-------------------------------------------------------------------------------- +Vector2d = { + x = 0, + y = 0, + pattern = '{x=%d,y=%d}', +} +Vector2d.__index = Vector2d + +---Create a new Vector2d and return it +---@param x integer +---@param y integer +---@return Vector2d +function Vector2d.new( x, y ) + if x == nil or type( x ) ~= "number" then return nil end + if y == nil or type( y ) ~= "number" then return nil end + local o = { x = math.floor( x ), y = math.floor( y ) } + setmetatable( o, { __index = Vector2d } ) + return o +end + + +-------------------------------------------------------------------------------- +-- SizeLimitedList +-------------------------------------------------------------------------------- +SizeLimitedList = {} +SizeLimitedList.__index = SizeLimitedList + +function SizeLimitedList.new( maxSize ) + local self = setmetatable( { + first = 0, + maxSize = 10, + currSize = 0, + items = {}, + + maxVal = nil, + minVal = nil, + + + }, SizeLimitedList ) + if maxSize ~= nil then + self.maxSize = maxSize + end + + return self +end + +function SizeLimitedList:setSize( newSize ) + if self.currSize > newSize then + local shrinkBy = self.currSize - newSize + local newFirst = self.first + shrinkBy + + while self.first < newFirst do + self.items[ self.first ] = nil + self.first = self.first + 1 + end + end + + self.currSize = math.min( self.currSize, newSize ) + self.maxSize = newSize +end + +function SizeLimitedList:getSize() + return self.currSize +end + +function SizeLimitedList:getMaxSize() + return self.maxSize +end + +function SizeLimitedList:add( item ) + self.items[ self.first + self.currSize ] = item + + if self.currSize < self.maxSize then + self.currSize = self.currSize + 1 + else + self.items[ self.first ] = nil + self.first = self.first + 1 + end + + self:updateMinMaxVals() +end + +function SizeLimitedList:getMinVal( default ) + return self.minVal or default +end + +function SizeLimitedList:getMaxVal( default ) + return self.maxVal or default +end + +function SizeLimitedList:iterate( f ) + local index = self.first + local sentinel = self.first + self.currSize + + while index < sentinel do + f( self.items[ index ] ) + index = index + 1 + end +end + +function SizeLimitedList:updateMinMaxVals() + self.minVal = nil + self.maxVal = nil + + self:iterate( + function( currVal ) + if self.minVal == nil then + self.minVal = currVal + else + self.minVal = math.min( self.minVal, currVal ) + end + + if self.maxVal == nil then + self.maxVal = currVal + else + self.maxVal = math.max( self.maxVal, currVal ) + end + end + ) +end + + +-------------------------------------------------------------------------------- +-- ScreenElement +-------------------------------------------------------------------------------- +--[[ + Offers the regular GPU T2 drawing commands, translating + the coordinates to a screen location. + + This serves as the base for a graphics library, allowing + multiple elements to be added that will (re)draw when + this Element is being (re)drawn +]] +ScreenElement = { + gpu = nil, + position = nil, + dimensions = nil, + subElements = {}, + + -- Helper functions at the bottom of this script + reposition = nil, +} + +function ScreenElement:new( o ) + o = o or {} + self.__index = self + setmetatable( o, self ) + + return o +end + +function ScreenElement:init( gpu, position, dimensions ) + self.gpu = gpu + self.position = position + self.dimensions = dimensions +end + +function ScreenElement:addElement( e ) + if e == nil then + return + end + + table.insert( self.subElements, e ) +end + +-- Draw the element and all sub elements that have been added +function ScreenElement:draw() + print( "Repainting" ) + for _, element in pairs(self.subElements) do + element:draw() + end +end + +function ScreenElement:flush() + computer.error( 'ScreenElement:flush() should not be called; call draw(), then do a gpu:flush()' ) +end + +function ScreenElement:measureText( Text, Size, bMonospace ) + return self.gpu:measureText( Text, Size, bMonospace ) +end + +--- Draws some Text at the given position (top left corner of the text), text, size, color and rotation. +---@param position Vector2D @The position of the top left corner of the text. +---@param text string @The text to draw. +---@param size number @The font size used. +---@param color Color @The color of the text. +---@param monospace boolean @True if a monospace font should be used. +function ScreenElement:drawText( position, text, size, color, monospace ) + self.gpu:drawText( + self:reposition( position ), + text, + size, + color, + monospace + ) +end + +--- Draws a Rectangle with the upper left corner at the given local position, size, color and rotation around the upper left corner. +---@param position Vector2D @The local position of the upper left corner of the rectangle. +---@param size Vector2D @The size of the rectangle. +---@param color Color @The color of the rectangle. +---@param image string @If not empty string, should be image reference that should be placed inside the rectangle. +---@param rotation number @The rotation of the rectangle around the upper left corner in degrees. +function ScreenElement:drawRect( position, size, color, image, rotation ) + self.gpu:drawRect( + self:reposition( position ), + size, + color, + image, + rotation + ) +end + +--- Draws connected lines through all given points with the given thickness and color. +---@param points Vector2D[] @The local points that get connected by lines one after the other. +---@param thickness number @The thickness of the lines. +---@param color Color @The color of the lines. +function ScreenElement:drawLines( points, thickness, color ) + if #points < 2 then + return + end + + local newPoints = {} + + for _, currPoint in pairs( points ) do + table.insert( newPoints, self:reposition( currPoint ) ) + end + + self.gpu:drawLines( + newPoints, + thickness, + color + ) +end + +function ScreenElement:reposition( vector ) + return Vector2d.new( + self.position.x + vector.x, + self.position.y + vector.y + ) +end + + +-------------------------------------------------------------------------------- +-- Plotter +-------------------------------------------------------------------------------- +Plotter = ScreenElement:new() + +Plotter.__index = Plotter +Plotter.graph = nil -- The graph this plotter belongs to +Plotter.maxVal = nil +Plotter.scaleFactorX = nil +Plotter.color = Color.GREY_0500 +Plotter.lineThickness = 10 +Plotter.dataSource = {} + +function Plotter.new( o ) + local plotter = setmetatable( o or {}, Plotter ) + + if o ~= nil and rawget( o, 'dataSource' ) ~= nil then + plotter:setDataSource( o.dataSource ) + end + + return plotter +end + +function Plotter:setDataSource( dataSource ) + self.dataSource = dataSource + self.scaleFactorX = self.graph.dimensions.x / ( self.dataSource:getMaxSize() - 1 ) +end + +function Plotter:setColor( color ) + self.color = color +end + +function Plotter:setLineThickness( lineThickness ) + self.lineThickness = lineThickness +end + +function Plotter:draw() + local i = 0 + local points = {} + self.dataSource:iterate( + function( currVal ) + local xPos = ( i + self.dataSource.maxSize - self.dataSource.currSize ) * self.scaleFactorX + local yPos = self.graph.dimensions.y - currVal * self.graph.scaleFactorY + + local position = Vector2d.new( xPos, yPos ) + table.insert( points, position ) + i = i + 1 + end + ) + + self:drawLines( points, self.lineThickness, self.color ) +end + + +-------------------------------------------------------------------------------- +-- Graph +-------------------------------------------------------------------------------- +Graph = ScreenElement:new() + +Graph.__index = Graph +Graph.scaleFactorY = nil +Graph.maxVal = nil +Graph.dimensions = nil +Graph.scaleMarginFactor = 0.2 +Graph.dataSources = {} +Graph.plotters = {} + +function Graph.new() + return setmetatable( {}, Graph ) +end + +function Graph:addPlotter( name, config ) + local plotter = Plotter.new() + self.plotters[ name ] = plotter + + if config ~= nil then + self:configurePlotter( name, config ) + end +end + +function Graph:configurePlotter( name, config ) + local plotter = self.plotters[ name ] + for k, v in pairs( config ) do + plotter[ k ] = v + end + plotter.graph = self + + if rawget( plotter, 'dataSource' ) ~= nil then + plotter:setDataSource( config.dataSource ) + table.insert( self.dataSources, config.dataSource or {} ) + end +end + +function Graph:setMaxVal( maxVal ) + self.maxVal = maxVal + self.scaleFactorY = self.dimensions.y / maxVal +end + +function Graph:setDimensions( dimensions ) + self.dimensions = dimensions + + for _,currItem in ipairs( self.dataSources ) do + currItem.dimensions = dimensions + end +end + +function Graph:draw() + if self.maxVal == nil then + self:autoResize() + end + + for _, plotter in pairs( self.plotters ) do + plotter:draw() + end +end + +function Graph:autoResize() + local maxVal = self:getMaxVal() + + if self.scaleFactorY == nil then + return self:initScaleFactors( maxVal ) + end + + local maxDisplayableVal = self.dimensions.y / ( self.scaleFactorY or 0.00000000001 ) + if + maxDisplayableVal < maxVal or + maxDisplayableVal * self.scaleMarginFactor > maxVal + then + self:initScaleFactors( maxVal ) + end +end + +function Graph:initScaleFactors( maxVal ) + if maxVal == nil then + maxVal = 0.00000000001 + end + + self.scaleFactorY = self.dimensions.y / ( maxVal * ( 1 + self.scaleMarginFactor ) ) +end + +function Graph:getMaxVal() + local maxVal = nil + for _,currItem in ipairs( self.dataSources ) do + maxVal = math.max( maxVal or currItem:getMaxVal(), currItem:getMaxVal() ) + end + return maxVal +end + + +-------------------------------------------------------------------------------- +-- Footer +-------------------------------------------------------------------------------- +Footer = ScreenElement:new() +Footer.fontSize = 50 +Footer.textColor = Color.GREY_0750 +Footer.textVerticalOffset = -22 + +function Footer:draw() + self:drawRect( Vector2d.new( 100,50 ), Vector2d.new( 50,50 ), self.colors.consumption, nil, nil ) + self:drawText( Vector2d.new( 200,50+self.textVerticalOffset ), self._getLabel( "Consumption", self.values.consumption), self.fontSize, self.textColor) + + self:drawRect( Vector2d.new( 100,150 ), Vector2d.new( 50,50 ), self.colors.production, nil, nil ) + self:drawText( Vector2d.new( 200,150+self.textVerticalOffset ), self._getLabel( "Production", self.values.production), self.fontSize, self.textColor) + + self:drawRect( Vector2d.new( 1100,50 ), Vector2d.new( 50,50 ), self.colors.maxConsumption, nil, nil ) + self:drawText( Vector2d.new( 1200,50+self.textVerticalOffset ), self._getLabel( "Max. consumption", self.values.maxPowerConsumption), self.fontSize, self.textColor) + + self:drawRect( Vector2d.new( 1100,150 ), Vector2d.new( 50,50 ), self.colors.capacity, nil, nil ) + self:drawText( Vector2d.new( 1200,150+self.textVerticalOffset ), self._getLabel( "Production capacity", self.values.capacity), self.fontSize, self.textColor) +end + +function Footer:setValues(values) + self.values = values +end + +function Footer._getLabel( text, value ) + if value == nil then + value = 'NaN' + else + value = string.format( '%.1f', value ) + end + + return text .. ' ' .. value .. ' MW' +end + + +-------------------------------------------------------------------------------- +-- BatteryInfo +-------------------------------------------------------------------------------- +BatteryInfo = ScreenElement:new() +BatteryInfo.line = 0 +BatteryInfo.lineHeight = 60 +BatteryInfo.fontSize = 35 +BatteryInfo.textColor = Color.GREY_0750 +BatteryInfo.dataFontSize = 50 +BatteryInfo.dataColor = Color.WHITE +BatteryInfo.colorBad = Color.RED +BatteryInfo.colorGood = Color.new( 0.000, 1.000, 0.000, 1.0 ) + +function BatteryInfo:draw() + -- We get the circuit inside the loop so that stuff doesn't crash whenever a wire is detached + local circuit = self.connector:getCircuit() + + if not circuit.hasBatteries then + return + end + + local batteryCapacity = ( circuit and circuit.batteryCapacity ) or 0 + local batteryStore = ( circuit and circuit.batteryStore ) or 0 + local batteryStorePercent = ( circuit and 100 * circuit.batteryStorePercent ) or 0 + local batteryTimeUntilFull = ( circuit and circuit.batteryTimeUntilFull ) or 0 + local batteryTimeUntilEmpty = ( circuit and circuit.batteryTimeUntilEmpty ) or 0 + local batteryIn = ( circuit and circuit.batteryIn ) or 0 + local batteryOut = ( circuit and circuit.batteryOut ) or 0 + + self.line = 0 + + self:print( 'Stored' ) + self:print( string.format( '%.1f %%', batteryStorePercent ), self.dataFontSize, self.percentageColor( batteryStorePercent ) ) + self.line = self.line + 2 + + self:print( 'Charge' ) + if( batteryStorePercent == 100 ) then + self:print( string.format( '%.1f MWh', batteryStore ), self.dataFontSize, self.percentageColor( batteryStorePercent ) ) + else + self:print( string.format( '%.1f / %.1f MWh', batteryStore, batteryCapacity ), self.dataFontSize, self.percentageColor( batteryStorePercent ) ) + end + self.line = self.line + 2 + + if batteryOut > 0 then + self:print( 'Discharge rate' ) + self:print( string.format( '%.1f MW', batteryOut ), self.dataFontSize, self.colorBad ) + self.line = self.line + 2 + elseif batteryIn > 0 then + self:print( 'Charge rate' ) + self:print( string.format( '%.1f MW', batteryIn ), self.dataFontSize, self.colorGood ) + self.line = self.line + 2 + end + + if batteryTimeUntilEmpty > 0 then + self:print( 'Time until empty ' ) + self:print( self.formatTime( batteryTimeUntilEmpty ), self.dataFontSize, self.colorBad ) + self.line = self.line + 2 + elseif batteryTimeUntilFull > 0 then + self:print( 'Time until full' ) + self:print( self.formatTime( batteryTimeUntilFull ), self.dataFontSize, self.colorGood ) + self.line = self.line + 2 + end +end + +function BatteryInfo:print( text, size, color ) + self.line = self.line + 1 + + local yPos = self.line * self.lineHeight + + size = size or self.fontSize + color = color or self.textColor + self:drawText( Vector2d.new( 40,yPos ), text, size, color) +end + +function BatteryInfo.formatTime( seconds ) + return string.format( + "%02d:%02d:%02d", + math.floor( seconds / 3600 ) % 24, + math.floor( seconds / 60 ) % 60, + math.floor( seconds % 60 ) + ) +end + +function BatteryInfo.percentageColor( percentage ) + if percentage < 33 then + return Color.RED + elseif percentage < 80 then + return Color.FICSIT_ORANGE + elseif percentage < 100 then + return Color.GREEN + else + return Color.GREY_0750 + end +end + + +-------------------------------------------------------------------------------- +-- PowerMonitor +-------------------------------------------------------------------------------- +PowerMonitor = { + connector = nil, + gpu = nil, + pollInterval = 1, + + productionList = nil, + consumptionList = nil, + maxPowerConsumptionList = nil, + + powerGraph = nil, + footer = nil, + + colors = { + consumption = Color.FICSIT_ORANGE, + capacity = Color.GREY_0500, + production = Color.GREY_0750, + maxConsumption = Color.new( 0.050, 0.500, 0.700, 1.0 ), + }, + + graphWidth = 2300, + graphHeight = 1550, +} + +function PowerMonitor:init( power, gpu ) + print( "\nInitialising PowerMonitor\n" ) + + self.gpu = gpu + local connectors = power:getPowerConnectors() + if #connectors == 0 then + computer.panic( 'The power interface has no connectors, cannot continue' ) + end + self.connector = connectors[1] + + self:initLists() + + -- Screen size is 3000x1800 + self.powerGraph = Graph.new() + self.powerGraph:setDimensions( Vector2d.new( self.graphWidth,self.graphHeight ) ) + self.powerGraph:addPlotter( + 'consumption', + { + gpu = self.gpu, + position = Vector2d.new( 0,0 ), + color = self.colors.consumption, + dataSource = self.consumptionList, + } + ) + self.powerGraph:addPlotter( + 'capacity', + { + gpu = self.gpu, + position = Vector2d.new( 0,0 ), + color = self.colors.capacity, + dataSource = self.capacityList, + } + ) + self.powerGraph:addPlotter( + 'production', + { + gpu = self.gpu, + position = Vector2d.new( 0,0 ), + color = self.colors.production, + dataSource = self.productionList, + } + ) + self.powerGraph:addPlotter( + 'maxConsumption', + { + gpu = self.gpu, + position = Vector2d.new( 0,0 ), + color = self.colors.maxConsumption, + dataSource = self.maxPowerConsumptionList, + } + ) + + self.batteryInfo = BatteryInfo:new({ connector = self.connector }) + self.batteryInfo:init( self.gpu, Vector2d.new( self.graphWidth,0 ), Vector2d.new( 3000-self.graphWidth,1800 ) ) + + self.footer = Footer:new({colors = self.colors}) + self.footer:init( self.gpu, Vector2d.new( 0, self.graphHeight ), Vector2d.new( self.graphWidth, 1000 ) ) +end + +function PowerMonitor:run() + print( "PowerMonitor running") + + while true do + self:collectData() + + -- Paint background + -- self.gpu:drawRect( Vector2d.new(0,0), Vector2d.new(3000,1800), Color.WHITE, nil, nil ) + + self.gpu:drawLines( + { Vector2d.new( 0,self.graphHeight ), Vector2d.new( self.graphWidth,self.graphHeight ) }, + 5, + Color.GREY_0500 + ) + self.gpu:drawLines( + { Vector2d.new( self.graphWidth,0 ), Vector2d.new( self.graphWidth,self.graphHeight ) }, + 5, + Color.GREY_0500 + ) + + self.powerGraph:draw() + + self.footer:setValues({ + production = self.production, + capacity = self.capacity, + consumption = self.consumption, + maxPowerConsumption = self.maxPowerConsumption, + }) + self.footer:draw() + + self.batteryInfo:draw() + + self.gpu:flush() + event.pull( self.pollInterval ) + end +end + +function PowerMonitor:initLists() + self.productionList = SizeLimitedList.new( 100 ) + self.capacityList = SizeLimitedList.new( 100 ) + self.consumptionList = SizeLimitedList.new( 100 ) + self.maxPowerConsumptionList = SizeLimitedList.new( 100 ) +end + +function PowerMonitor:collectData() + -- We get the circuit inside the loop so that stuff doesn't crash whenever a wire is detached + local circuit = self.connector:getCircuit() + + self.production = ( circuit and circuit.production ) or 0 + self.capacity = ( circuit and circuit.capacity ) or 0 + self.consumption = ( circuit and circuit.consumption ) or 0 + self.maxPowerConsumption = ( circuit and circuit.maxPowerConsumption ) or 0 + + self.productionList:add( self.production ) + self.capacityList:add( self.capacity ) + self.consumptionList:add( self.consumption ) + self.maxPowerConsumptionList:add( self.maxPowerConsumption ) +end + +-------------------------------------------------------------------------------- + +local power = getComponentsByClass( { + "FGBuildablePowerPole", -- Power poles and wall outlets + "CircuitSwitch", + "Build_PriorityPowerSwitch_C", + "PowerStorage", +} ) +if #power == 0 then + computer.panic( "No power pole or wall outlet hooked up; nothing to monitor" ) +end +power = power[1] + +local gpu = computer.getPCIDevices( classes.GPU_T2_C )[1] +if gpu == nil then + computer.panic( "No GPU T2 found. Cannot continue." ) +end + +local computerSettings = settingsFromComponentNickname( computer.getInstance() ) +local screens = getComponentsByClassAndNick( { + "ModuleScreen_C", + "Build_Screen_C", +}, computerSettings.screen or '' ) +if #screens == 0 then + computer.panic( "No screen found. Cannot continue." ) +end + +gpu:bindScreen( screens[1] ) + +screenSize = gpu:getScreenSize() +print( 'Screen resolution: ' .. screenSize.x .. 'x' .. screenSize.y ) + +PowerMonitor:init( power, gpu ) +PowerMonitor:run() diff --git a/Packages/PowerMonitor/v0.0.1/metadata.toml b/Packages/PowerMonitor/v0.0.1/metadata.toml new file mode 100644 index 0000000..72b440b --- /dev/null +++ b/Packages/PowerMonitor/v0.0.1/metadata.toml @@ -0,0 +1,6 @@ +fin_version = ">= 0.3.19" + +[[EEPROM]] +name = "EEPROM.lua" +title = "EEPROM" +description = "The contents of EEPROM.lua will have to be copied to your EEPROM." diff --git a/README.adoc b/README.adoc index 6da939f..c3e74cd 100644 --- a/README.adoc +++ b/README.adoc @@ -1,14 +1,14 @@ = FicsIt-Networks-Repository -This is a repository/collection of Software Packages for use with the link:https://github.com/Panakotta00/FicsIt-Networks[FicsIt-Networks] Satisfactory mod. +This is a repository/collection of software packages for use with the link:https://github.com/Panakotta00/FicsIt-Networks[FicsIt-Networks] Satisfactory mod. -It contains Templates, Full-Fledged Programs and Examples provided by the Mod-Authors and the Mods Community. +It contains templates, full-fledged programs and examples provided by the mod's authors and the mod's community. == Publish a Package -Anyone with a GitHub Account can publish a Package. +Anyone with a GitHub account can publish a package. -Create Fork of this Git Repository and add a Folder with an Alphanumeric Name (`[a-zA-Z0-9_\-]+`) to the link:/Repository[`Repository`] Folder. +Create fork of this git repository and add a folder with an alphanumeric name (`[a-zA-Z0-9_\-]+`) to the link:/Repository[`Repository`] folder. In this newly created folder create a `README.adoc` or `README.md` containing a description of your package. + Add a `EEPROM.lua` file containing your EEPROM Code. + @@ -18,20 +18,20 @@ TIP: You can look at already submitted packages for examples of package structur NOTE: Alternatively you can copy the link:/Repository/ExamplePackage[Example Package], change the folder and fill in the files with your own data. -After you have comitted your changes to your Fork, you can create a Pull-Request to this Repository on the main branch. + -Once your PR gets merged, your Package will be available in the FicsIt-Repository. +After you have comitted your changes to your fork, you can create a pull-request to this repository on the main branch. + +Once your PR gets merged, your package will be available in the FicsIt-Repository. -If you want to update your software, create a copy of your Package-Folder contents, and put them into a newly created folder inside your Package-Folder with the version "old" version-number prefixed with `v` as folder name. + -e.g. if you had a package `MyPackage` with the version `1.2.3`, create a Folder called `v1.2.3` and copy your contents in there. + +If you want to update your software, create a copy of your package folder contents, and put them into a newly created folder inside your package folder with the version "old" version-number prefixed with `v` as folder name. + +e.g. if you had a package `MyPackage` with the version `1.2.3`, create a folder called `v1.2.3` and copy your contents in there. + After that you can alter your package however you want. -CAUTION: Ensure you have increased your version number in the metadata file. +CAUTION: Ensure you have increased your version number in the metadata file. == Work-In-Progress -This Repository is work-in-progress. + -And Package defintion may change over time. +This repository is work-in-progress. + +And package definition may change over time. Already submitted packages will get converted to new defintions using manual or automatic migration procedures if possible. -There will be a Web and In-Game Interface to browser the Repository more user-friendly. +There will be web and in-game interfaces to browse the repository in more user-friendly way.