Warning when laptop is on battery power

Reason:

Sometimes my cable of the charger of my laptop is either lose or forgotten to connect. Instead of waiting until the laptop dies during a gig, I want to see fast feedback. Since GP is normally maximized, I don’t see the battery icon (and even then, it’s really small).

Solution
An external (C#) application writes a single line text with battery status information to a dedicated file. The code is shown later.

Two widgets are shown only when the charger is disconnected, and shows the battery percentage and how many hours are remaining.

Default rackspace
I built in the solution in my default rackspace, but here I only put the necessary parts. Note that the gig file will follow when I finished my default rackspace. Or at least have it to a certain ‘good’ state (this can take many months).

Screenshot
Below a screenshot is shown when the charger is disconnected, see the orange oval

External application
This is a WinForms C# application with the following source code for the Forms.cs file.
For this, a (free) Visual Studio installation is needed. It does not have to be created on the GP laptop you are using. However, to use the application on the GP laptop, a library with the DotNet library needs to be installed (running the application will automatically handle this).


using System;
using System.IO;
using System.Windows.Forms;

// Writes file with content: <state>,<percentage in 0.00-1.00>,<seconds remaining>
// PC                      : Online,NoSystemBattery,1,-1   (No battery, 100%, -1 (infinite) sec)
// Laptop connected        : Online,High,1,-1              (High, 100%, -1 (infinite) sec)
// Laptop charging         : Online,Charging,0,2,-1
// Laptop unconnected      : Offline,High,0,97,7094         (High, 97%, 7094 sec)
//                           Offline,High,0,96,7151         (High, 96%, 7151 sec)
//                           Offline,0,0,54,...             (100-75%: High, 75-25%: -, 25-0%: Low)
//                           Offline,Low,0,24,1753          (Low, 24%, 1753 sec)
// Sec

namespace WinFormsAppBattery
{
    public partial class Form1 : Form
    {
        const string FILE_NAME = @"D:\Info\battery_status.txt";
        const int INTERVAL_TIME = 60 * 1000; // in ms

        static readonly System.Windows.Forms.Timer myTimer = new Timer();


        private static string CreateBatteryStatusString(PowerStatus pw)
        {
            return 
             pw.PowerLineStatus.ToString() + "," +
             pw.BatteryChargeStatus.ToString() + "," + 
             pw.BatteryLifePercent.ToString() + "," +
             pw.BatteryLifeRemaining.ToString();
        }


        private static async void TimerEventProcessor(object myObject, EventArgs myEventArgs)
        {
            string newBatteryStatus = CreateBatteryStatusString(SystemInformation.PowerStatus);
            string fileBatteryStatus = File.ReadAllLines(FILE_NAME)[0];
            if (newBatteryStatus != fileBatteryStatus)
            {
                await File.WriteAllLinesAsync(FILE_NAME, new string[] { newBatteryStatus } );
            }
        }


        public Form1()
        {
            InitializeComponent();
        }


        private void Form1_Load(object sender, EventArgs e)
        {
            myTimer.Tick += new EventHandler(TimerEventProcessor);
            myTimer.Interval = INTERVAL_TIME;
            myTimer.Start();
        }
    }
}

Change the file path in the external file above and in the GP Script equally.

Rackspace (Panel) Changes
Create two widgets with the names according to shown in the GP Script.

GP Script

var
	WarningBatteryPowered, WarningBatteryStatus : Widget
	
	BATTERY_WIDGETS : Widget array = [ WarningBatteryPowered, WarningBatteryStatus ]
	
	LastBatteryCheckTime : double = 0.0


function SecondsToHours(totalSeconds : integer) returns String
	var hours : integer
		minutes : integer
		seconds : integer
		
	hours = totalSeconds / 60 / 60
	minutes = (totalSeconds - hours * 60 * 60) / 60
	seconds = totalSeconds % 60
	
	result = IntToString(hours) + ":"
	if minutes < 10 then
		result = result + "0" 
	end
	result = result + IntToString(minutes) + ":"
	if seconds < 10 then
		result = result + "0" 
	end
	result = result + IntToString(seconds)
end


on TimerTick(ns : double)
    var 
		fileName : string
		batteryStatus : string
		batteryStatusParts : string array
		batteryLabel : string
		

	if ns > LastBatteryCheckTime + 1000  then
		fileName = "D:/Info/battery_status.txt"
   
		batteryStatus = LoadStringFromTextFile(fileName)
		batteryStatusParts = SplitString(batteryStatus, ",")
		
		select batteryStatusParts[0] == "Online" do
			ShowWidgets(BATTERY_WIDGETS, false)
		end
		select batteryStatusParts[0] == "Offline" do
			ShowWidgets(BATTERY_WIDGETS, true)
			if batteryStatusParts[2] == "1" then
				batteryLabel = "100%"
				if batteryStatusParts[3] != "-1" then
					batteryLabel = batteryLabel + "  " + 
					 SecondsToHours(StringToInt(batteryStatusParts[3])) + " remaining" 
				end
			else
				batteryLabel = 
				 IntToString(StringToInt(batteryStatusParts[3])) + "%"
				if batteryStatusParts[4] != "-1" then
					batteryLabel = batteryLabel + "  " + 
					 SecondsToHours(StringToInt(batteryStatusParts[4])) + " remaining"
				end
			end
			
			SetWidgetLabel(WarningBatteryStatus, batteryLabel)
		end

		LastBatteryCheckTime = ns
	end
end

Improvements
Improve some duplicated code, and automatically make the external application start.

Disclaimer
Note that this script is not tested much.

Solution Comparison

Solution               Author         Advantages              Disadvantages
----------             -------------- ----------------------  -------------------------
Ext. utility           npudar         Easy installation       No direct GP interaction
                                                                 possible
                                      No scripting needed     Hardcoded GUI
Extension library      Frank1119      Encapsulated in library   
Ext. app with OSC      Spav           OSC flexibility         External app needed
Ext. app with file I/O Michelkeijzers -                       External app needed
                                                              File I/O

4 Likes

Thanks, I added this to the hacks compilation here.

I used the free Battery Bar widget, which can be placed anywhere on the screen:

Link: https://community.gigperformer.com/t/see-laptop-charging-status-within-gp/12626/10?u=npudar

2 Likes

That one is nice too, although I like mine better (more visible) … but it would save me a few hours of work and my solution is less efficient (file I/O needed).

Thanks for adding it to the hacks compilation.

1 Like

Might someone be interested (Windows Only!): I’ve written an extension library for GP exporting some script functions to retrieve some Battery Stats. Usage:

//$<AutoDeclare>
//$<AutoDeclare>
// DO NOT EDIT THIS SECTION MANUALLY
Var
   Display : Widget
   ReqBatt : Widget
   DisplayLife : Widget
//$</AutoDeclare>

// Called when a single widget value has changed
On WidgetValueChanged(newValue : double) from ReqBatt
var Bpct : int = 0
    BChg : boolean = false
    BDchg : boolean  = false
    BPrsnt : boolean  = false
    BStat : int = 0
    BLife : int = 0
    disp : string = ""
    
    if newValue != 0
    then
        Bpct  =  BatteryInfo_GetBatteryPercent()
        BChg  =  BatteryInfo_GetBatteryCharging()
        BDchg  =  BatteryInfo_GetBatteryDischarging()
        BPrsnt  =  BatteryInfo_GetBatteryPresent()
        BStat = BatteryInfo_GetACLineStatus()
        BLife = BatteryInfo_GetBatteryLifeTime()

        disp = "Percent " + Bpct +" Charging " + BChg + " Discharging " + BDchg + " Present " + BPrsnt + " State " + BStat

        Display.SetWidgetLabel(disp )
        DisplayLife.SetWidgetLabel("Remaining seconds " + BLife )
    end
End

This is the extension. It comes with the usual disclaimer: Use at your own risk. Always scan the file first with a malware scanner and try it first in a non-production environment!

This is version 1.0, but it is a beta version :slight_smile: I will upload the source code to github.

I hope someone likes it.

Update: Found a bug. The GetACLineStatus() does not retrieve the current status. However, all other functions do, so calling one of these will also refresh the state for this function. Corrected in this zip:

SHA256: 2580A691C14E83FDD6C2FC7651AE467073876684E5DE068E482DB5A0D8A521D0

BatteryInfo-Ext-4.5.8.zip (12.1 KB)

6 Likes

This is very interesting… Thanks

Please do :slight_smile:
And here is the dedicated thread for this Extension → LINK

2 Likes
3 Likes

Thank you, and all the users who do scripting for the GP community.

Jeff

2 Likes

This thread I liked, having had someone unplug a laptop PSU and not plug it back in!!!
I have a zero GP Script version that should work cross platform as it uses a python library that is supported on Windows, OSX and Linux, and uses OSC to deliver the battery status.

Add a text widget to the global rackspace and name it Battery. Enable OSC for the widget and it will set an OSC address of /GlobalRackspace/Battery/SetValue. You should also ensure that OSC is enabled from the menu Options/OSC Setup. Note the script is using SetCaption and not SetValue to change the text of the widget.

The following python oscbattery.py script can be run without any command line parameters if on the same machine as GP, if the defaults ports have been used and the widget value has been set to “Battery” in a global rackspace. It will update the battery status once per minute by default.

# Read battery percentage and send OSC message to Gig Performer
import psutil
from pythonosc import udp_client
import argparse
import time

if __name__ == '__main__':

    # Set the defaults and grab command line parameters if present
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="127.0.0.1", help="The ip of the OSC server")
    parser.add_argument("--port", type=int, default=54344, help="The port the OSC server is listening on")
    parser.add_argument("--interval", type=int, default=60, help="The number of seconds between updates")
    parser.add_argument("--widget", default="/GlobalRackspace/Battery/SetCaption", help="The OSC path to the widget")
    args = parser.parse_args()

    # Serve the OSC messages to Gig Performer
    client = udp_client.SimpleUDPClient(args.ip, args.port)
    while 1:
        battery = psutil.sensors_battery()
        plugged = "Plugged In" if battery.power_plugged else "Not Plugged In"
        client.send_message(args.widget, (str(battery.percent) + "% | " + plugged))
        time.sleep(args.interval)

A couple of points to note when running python on the MAC: the OS requires python to be present, so check the version of python preinstalled using python --version if this is python 3.x.x then all should be good. If the version is python 2.x.x then python 3 will need to be installed, DO NOT UNINSTALL python 2.x.x. Two additional modules will need to be installed, using pip3 install psutil pythonosc. The pythonosc module requires Python 3 to function correctly.

The python script can then be launched from a command line terminal with python3 oscbattery.py, but the terminal will remain. With further effort it could be written to run as a service.

With very few changes this code can be used to read fanspeed, temperatures, memory etc…

6 Likes

Very nice approach, thanks for sharing.

I think my solution is the ‘worst’ (as it uses file I/O), using your OSC method of the extension library from Frank1119 are better.

Still I’m glad to have posted mine, as it gave better alternatives, useful for many others.
(I have a very busy week and no time to check them out, but that’s irrelevant for this thread).

So, for someone (me) who is technically not as proficient as you guys and has never used OSC or Global Rackspace, do you think the free app suggested by Nemanja might by the way to go?

Or is there a real benefit of using Frank’s approach?

Jeff

1 Like

Don’t know about benefits. All GP based solutions need you to do some scripting. You might consider that to be an advantage, because you can provide a clear warning in the GP gui and make it scream at you as much as you like. The scripting is fairly straight forward. Once ‘installed’ the extension is automatically always available. Other solutions has to be started separately.

I’m not going to give advice, as I’ve written the extension :smiling_face:

2 Likes

Thanks! (I think the point that it automatically starts working whenever the user opens GP is a big benefit.)

I created in my first post a comparison table (hope all agree, if not, I can change it). All three remaining solutions have their own advantages (and disadvantages).

1 Like

Sorry I’m a bit needy, but could you explain the downside of “Hardcoded GUI” for the app Nemanja referenced.

Could I set that app (referenced by Nemanja) up so it always turns on whenever I turn on my computer and stays in the same location (in front of the GP interface). If so, I think that would work for me.

Jeff

This would be a nice idea for an extension :innocent:

Yes, you can start up apps automatically (in windows at least): Add an app to run automatically at startup in Windows 10 - Microsoft Support

Thank you!

This application is a stand alone application, the GUI cannot be changed (within GP), meaning the application itself defines the GUI, and only can be changed within the application (if it can). I can imagine some prefer to have the battery notification incorporated as a widget on a panel within GP.

1 Like