EOS & Cinnamon, an endeavour in itself (CUPS, Nextcloud, Home Assistant)

Phew. I decided for EOS w/ Cinnamon, out of pure laziness (been using Mint 12+ years). Considering making this not a plaything, but a machine to work with, I decided to fight this through (and learn new things).

It seems EOS defaults to KDE Plasma for a reason—in retrospect, KDE Plasma seems to be much more well-integrated, and it is much better than it was years ago, when I abandoned it.

Anyway, three more tasks done, with lots of looking-things-up and some experimentation:

  • CUPS network printer setup (Kyocera Ecosys P2135dn & P5021cdn).
    • In printer, enable IPP and Bonjour.
    • avahi and nss-mdns needed for driverless CUPS
    • don’t forget firewall (I did), allow ipp & mdns
  • Nextcloud Contacts, Calendar, Nemo integration (via WebDAV)
    • gnome-online-accounts (extra) & gnome-online-accounts-gtk (AUR)
    • In Nextcloud, under Personal Settings → Security → Devices & Sessions, create new App (I called it “Linux Online Accounts”), note password
    • In EOS Online Accounts, use base server name, not full dav path, i.e. https://cloud.example.com.
    • gnome-keyring (Login/default keyring PW == user PW to avoid extra keyring login when logging in; append password optional pam_gnome_keyring.so in /etc/pam.d/passwd to keep in sync.
    • Calendar & Contacts integration: gnome-calendar, gnome-contacts
    • For Nemo integration, gvfs-dnssd needed! (Otherwise “Cannot mount” error, also enables davs:// links).
  • Home Assistant laptop battery sensor: Adapt my Python script below (and put it in crontab [use cronie]). Nice for low battery alarm or switching off charger when full.

Et voilà: Seamless integration with my Nextcloud instance! Perfect printing, Nemo access to Nextcloud storage, Nextcloud Contacts & Calendar all integrated.

Never give up, never surrender! By Grabthar’s Hammer!


Little Python script (use */1 * * * * in crontab to run every minute) to keep Home Assistant updated on laptop battery status:

EDIT 2025-05-17: Updated script here → EOS & Cinnamon, an endeavour in itself (CUPS, Nextcloud, Home Assistant) - #4 by Moonbase59

I put this in /usr/local/bin/ha-battery-sensor and use this in root’s crontab:

*/1 * * * * /usr/local/bin/ha-battery-sensor > /dev/null 2>&1

A little off-topic, but if you want to do things “right” in Home Assistant, also create a template sensor (using HA’s GUI Helpers) like so:

Then you can enjoy your Linux laptop battery state on your battery panel—here’s mine as an example:

Have fun! :slight_smile:

5 Likes

are you using you script to save and import date about % battery and get history in webdav / nextcloud ?
( webdav / nextcloud can do more others things like backups and other things )
and what about tlp , more https://wiki.archlinux.org/title/Laptop#Battery_state

The lil Python script is just for Home Assistant fanatics like me—its only purpose is to update the battery status to HA (which in turn keeps track of the history).

I don’t have tlp installed on this laptop, but the script shouldn’t interfere, I think.

Letting Python’s psutil do the heavy lifting (i.e., what sensor to use on which platform) helps keeping the script OS-independent (it should work on Windoze, macOS, Linux).

My Nextcloud mainly provides contact and calendar data to all my systems (including the router’s phone book). The Nemo integration I sometimes use to quickly swap some files, but the davs: protocol and gvfs have some drawbacks, so I normally use NFS when at home (to my TrueNAS).

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. :slight_smile:

This is very interesting for me because: 1) I’m coming to EndeavourOS from five years on LinuxMint, and 2) I’ve been thinking about putting up a NextCloud server. My current server is for media (Plex) and file share (NFS) but it is local network only at this point.

Interestingly, I came to EndeavourOS because I was looking at KDE from a desire to move off of Cinnamon. I don’t like the direction they’re heading with it as manifested in Xia.