Just in case anyone else uses Home Assistant to track their laptop’s battery state: I’ve updated the script above, because Python’s psutil
couldn’t detect batteries on some oddball tablets/convertibles/notebooks. Assuming we run Linux on those, I added a fallback to use upower
in those cases.
#!/usr/bin/env python3
# encoding: utf-8
# ha-battery-sensor
# 2025-03-24 Moonbase59
# 2025-05-16 Moonbase59 - improve battery detection
# 2025-05-17 Moonbase59 - add upower check if psutil fails
from requests import post
import psutil
import json
import socket
import subprocess
# get hostname as device name
device_name = socket.gethostname().split('.')[0]
device_slug = socket.gethostname().split('.')[0].replace('-', '_').casefold()
# Home Assistant URL (use remote URL if it should work outside your local network)
# ha_url = 'http://homeassistant.local:8123'
# ha_url = 'https://homeassistant.example.com'
ha_url = 'https://homeassistant.example.com'
# Home Assistant long-lived accessed token (create one first!)
ha_token = 'very_long_homeassistant_token'
# Create sensor and friendly names
ha_sensor = 'sensor.' + device_slug + '_battery_level'
ha_friendly_name = device_name + ' Battery level'
url = ha_url + '/api/states/' + ha_sensor
headers = {
'Authorization': 'Bearer ' + ha_token,
'content-type': 'application/json',
}
# Try psutil first
if hasattr(psutil, "sensors_battery"):
try:
percent = psutil.sensors_battery().percent
charging = psutil.sensors_battery().power_plugged
except AttributeError:
battery = False
else:
battery = True
else:
battery = False
# If psutil failed, see if we can get data from upower (if installed)
# This will probably only work on Linux
# shell commands to use for upower (returns 'unknown' if upower isn't installed)
upower_cmd_percent = "command -v upower >/dev/null 2>&1 && upower -i $(upower -e | grep '/battery') | grep --color=never -E percentage|xargs|cut -d' ' -f2|sed s/%// || echo 'unknown'"
upower_cmd_state = "command -v upower >/dev/null 2>&1 && upower -i $(upower -e | grep '/battery') | grep --color=never -E state|xargs|cut -d' ' -f2 || echo 'unknown'"
if battery == False:
try:
percent = subprocess.check_output(upower_cmd_percent, shell=True, text=True)
percent = float(percent.strip())
state = subprocess.check_output(upower_cmd_state, shell=True, text=True)
state = state.strip()
charging = True if state == 'charging' else False
except:
battery = False
else:
battery = True
# prepare payload
if battery:
percent = round(percent, 0)
icon = 'mdi:battery'
icon0 = 'mdi:battery-outline'
icon100 = 'mdi:battery'
if charging:
icon = "mdi:battery-charging"
icon0 = "mdi:battery-charging-outline"
icon100 = "mdi:battery-charging-100"
step = round(percent / 10) * 10
if step < 10:
icon = icon0
elif step > 90:
icon = icon100
else:
icon = icon + '-' + str(step)
else:
icon = 'mdi:battery-unknown'
percent = 'unknown'
payload = {
'state': str(percent),
'attributes': {
'device_class': 'battery',
'state_class': 'measurement',
'unit_of_measurement': '%',
'friendly_name': ha_friendly_name,
'icon': icon
}
}
# print(json.dumps(payload))
response = post(url, headers=headers, data = json.dumps(payload))
# print(response.text)
Let me know if it worked for you, or what you had to change.