💾 Archived View for gemini.mcgillij.dev › py3status_ups.gmi captured on 2023-03-20 at 17:58:13. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2022-03-01)

-=-=-=-=-=-=-

Creating a py3status module to monitor my UPS battery

:author:

mcgillij

:category:

Python

:date:

2020-12-31 19:09

:tags:

UPS, Battery, Python, Tutorial, i3, #100DaysToOffload

:slug:

py3status-battery-monitor

:summary:

How to create a py3status module

:cover_image:

battery.jpg

Contents

I recently bought a

CyberPower battery backup

, since my apartment has crazy unreliable power, and so that my computers will stop shutting down improperly.

Setting up the UPS

Battery UPS’s are fairly well supported in Linux due to the popularity in the server world. To setup your UPS you will likely need to install your distributions version of the **nut** (Network UPS Tools) package.

In Arch I had to run the following command to get the packages required for the setup.

pacman -S nut usbutils

This installed the required packages and also the "lsusb" utility to allow some better debugging and fetching of some of the required information for configuration.

The **nut** package comes with a great utility to scan for batteries and essentially generate your configuration for you. I was able to run the "sudo nut-scanner" command to succesfully probe my battery and create the configuration file for it.

Here is the output of the above command for me:

[nutdev1]
     driver = "usbhid-ups"
     port = "auto"
     vendorid = "0764"
     productid = "0501"
     product = "LX1325GU"
     serial = "QAQJV2001010"
     vendor = "CPS"
     bus = "001"

Once you are able to probe your batter and make a configuration file for it, you can save it in "/etc/nut/ups.conf" and you can give it a name I choose to name it *battery*.

So my configuration in /etc/nut/ups.conf looks like:

[battery]
     driver = "usbhid-ups"
     port = "auto"
     vendorid = "0764"
     productid = "0501"
     product = "LX1325GU"
     serial = "QAQJV2001010"
     vendor = "CPS"
     bus = "001"

UPSD

Now we can setup **upsd** to allow us to query the server for the various battery stats and configurations.

The default configuration for me was fine in "/etc/nut/upsd.conf", so I just created a "/etc/nut/upsd.users" to create a user that I’ll be able to use in my i3 py3status plugin to query the battery status.

[myuser]
  password = sekretpassword
  upsmon master
  actions = SET
  instcmds = ALL

With this file in place we can start the service with "systemctl start nut-server.service". If everything came up alright you be able to query the status of the battery with "upsc battery" and it should output something like:

battery.charge: 100
battery.charge.low: 10
battery.charge.warning: 20
battery.mfr.date: CPS
battery.runtime: 420
battery.runtime.low: 300
battery.type: PbAcid
battery.voltage: 24.0
battery.voltage.nominal: 24
device.mfr: CPS
device.model: LX1325GU
device.serial: QAQJV2001010
device.type: ups
driver.name: usbhid-ups
driver.parameter.bus: 001
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 2
driver.parameter.port: auto
driver.parameter.product: LX1325GU
driver.parameter.productid: 0501
driver.parameter.serial: QAQJV2001010
driver.parameter.synchronous: no
driver.parameter.vendor: CPS
driver.parameter.vendorid: 0764
driver.version: 2.7.4
driver.version.data: CyberPower HID 0.4
driver.version.internal: 0.41
input.voltage: 115.0
input.voltage.nominal: 120
output.voltage: 139.0
ups.beeper.status: enabled
ups.delay.shutdown: 20
ups.delay.start: 30
ups.load: 52
ups.mfr: CPS
ups.model: LX1325GU
ups.productid: 0501
ups.realpower.nominal: 810
ups.serial: QAQJV2001010
ups.status: OL LB
ups.test.result: No test initiated
ups.timer.shutdown: -60
ups.timer.start: -60
ups.vendorid: 0764

We can also use **upsc** to querying for a single attribute like this "uspc battery ups.status" which output something like:

"OL LB"

So whats OL and LB mean? Those are NUT status codes returned from the UPS device itself. The **OL** means “Online”, and the **LB** means “Low battery”. Since I just got my UPS this makes sense, it’s still got to charge up the battery since they can’t ship them charged. You can find other

nut status codes here

So that will allow me to pull the status into my python module to get the status onto my desktop enviornment. However we can still setup a few more UPS related settings to make our lives easier in the future.

Finally we can **enable** the service with "systemctl enable nut-server.service".

UPSMON

Now that we can query our battery we can use **upsmon** to run commands based on the status of our UPS. In short we can tell it to turn off our computers gracefully after a power outtage instead of just yanking the power cord.

For this we will want to edit the following file "/etc/nut/upsmon.conf" and add a similar line to your configuration.

MONITOR battery@localhost 1 myuser sekretpassword master

From here you can start the service with "systemctl start nut-monitor.service" and if it’s configured properly after checking the *status* of it with "systemctl status nut-monitor.service", finally you can *enable* it with "systemctl enable nut-monitor.service"

This is the configuration where you can manage how your machine will shutdown once it’s running on battery power, you can also setup alerting or email notifications from here. However for me I just wanted my machines to shutdown properly as there’s nothing mission critical running on them.

i3 and py3status

This was about writing a monitor for my battery, so onward to that. On my desktop I’m running

i3

and as a replacement to the *i3bar* I’m running

py3status

Changing the default **bar** in *i3* is super simple, find your configuration file (usually in "~/.config/i3/config"), and update the following section.

bar {
     status_command i3status
}

and replace it with

bar {
     status_command py3status
}

By default *py3status* will use the default **i3status.conf**. So we can edit it and add a section for our custom python module.

I’m calling my python module **ups_battery_status** since there was already a module named battery in there that seems to be for laptops.

Adding my module to the **~/.config/i3/i3status.conf**

...
order += "uptime"
order += "arch_updates"
order += "ups_battery_status" #<<<<<<<<<< Our custom module
order += "volume_status"
...

Restarting *i3* now will essentially give us a placeholder where our module should be loaded since we haven’t written it yet.

Writing the module

In ".i3/py3status/" create a file called **ups_battery_status.py** as py3status checks that directory for custom modules on startup. Or you can fetch the file from my

Github account

.

The contents of the file is pretty simple, and there’s lots of room for improvements should you want to add some runtime configuration or more statistics, but for my purpose I just wanted to get the status onto my desktop.

# -*- coding: utf-8 -*-
"""
Module to report the battery level from my UPS

Dependencies: upsc
And assumes you have named your UPS 'battery'
If you named it something else you can set it below in the command
"""

class Py3status:
    cache_timeout = 600
    format = 'Battery: {status}'

    def _get_battery_status(self):
        try:
            status = self.py3.command_output(["upsc", "battery", "ups.status"])
            return {'status': status.strip()}

        except self.py3.CommandError as ce:
            return len(ce.output.splitlines())

    def ups_battery_status(self):

        status = self._get_battery_status()
        full_text = self.py3.safe_format(self.format, status)

        return {
            'full_text': full_text,
            'cached_until': self.py3.time_in(self.cache_timeout)
        }

That’s the entirety of the module, the magic is from "self.py3.command_output()" which just runs a terminal command and returns the output, we strip off any new lines and then just feed it back to the formatter. The

docs for py3status

are well written and easy to follow along.

Once that’s in place you can just restart i3 and you should now have the status of your battery available to you on your desktop.

It should look something like:

[image: battery status displayed in py3status bar]