Control your Samsung SmartThings smart home from the KDE Plasma panel — toggle lights, dim, run the AC, drive TVs and speakers, and read your sensors, without opening a phone app. A small local Python daemon talks to the SmartThings cloud and a QML plasmoid renders the controls.
- Panel icon with a badge showing how many devices are on, and a popup organized into tabs by device type: Luces · Cerraduras · Clima · Multimedia · Aparatos · Sensores · Botones · Hubs — each tab shows its count, empty ones stay hidden, and the bar is a single scrollable row rather than two wrapped ones.
- Search by device, room or plug name, plus a room filter; the grid is ordered by room, and each card shows the room it belongs to.
- Rename any device locally (✏ on hover): the new name lives only in this widget, never in the SmartThings cloud. See Local device names.
- Devices are classified by SmartThings component categories (the same
signal the official app uses), so a TV or AC — which also expose a
switch— never gets misfiled as a light. - Lights / switches / plugs: 4:3 tiles, one tap to toggle; multi-outlet plugs expand into individual tiles. The shorter tile fits three rows of devices where a square one fit two.
- Locks: closing is one tap, opening asks for confirmation first.
- Dimmers: brightness slider (
switchLevel). - Air conditioners: on/off, target temperature (−/+ within the device's range), mode (auto/cool/dry/fan/heat), plus current temperature & humidity.
- TVs: on/off, volume slider, play/pause/stop, current input.
- Speakers: play/pause/stop, volume, and what's currently playing.
- Appliances (washer/dryer…): on/off and machine state.
- Sensors: read-only temperature, humidity, motion/contact, battery — with a
desktop notification (
notify-send) when a motion sensor becomes active. - Surface instead of outline: cards have no resting border — a grid of outlined rectangles reads as a grille. The line comes back on hover and on keyboard focus. Tiles join the tab chain, so you can Tab to a device and toggle it with Space or Enter.
- Names in the desktop sans, everything else monospaced: uppercase + mono + 8 px flattens a name into another field, so device names use the Plasma UI font in sentence case and the terminal type is kept for labels, units and readouts.
- One colour per function: teal is structure (borders, tabs, chrome), cream is the device name, warm grey is secondary data, coral means on. A device that is on gets a warm wash plus a coral bar rather than a flood of coral — and one the hub can't reach is drawn with a diagonal hatch, so "off" and "unreachable" never look alike. Every text pair clears WCAG AA (the old dark-on-coral name was 4.16:1; the cream name is 11.8:1).
- Optimistic UI: taps apply instantly; the daemon patches its cache first and reconciles with the cloud a few seconds later, so controls never "bounce". A command that fails says so in the popup instead of silently reverting.
- Adaptive polling: the widget re-reads the daemon every 4 s while the popup
is open and every 30 s when closed; the daemon itself sweeps the cloud every
20 s. The refresh button (and middle-click on the panel icon) forces a real
sweep via
POST /refreshand spins until it lands — roughly 5 s for 33 devices, because the per-device calls run in a thread pool instead of one after another. The footer shows how old the cloud read is, not when the widget last re-read the cache. - Sizes and type follow the Plasma font settings, so the widget stays legible on HiDPI screens; the tile grid reflows to the widget's width.
- Runs as a systemd user service (starts at boot). The access token is refreshed automatically and never expires in practice.
- Optional PyQt6 system tray (
systray/systray.py) as an alternative to the plasmoid.
SmartThings tags every device component with categories. The daemon maps those (with priority over raw capabilities) to a widget type:
| Widget type | SmartThings category / capability | Controls |
|---|---|---|
| Light / Switch / Plug | Light, Switch, SmartPlug (+ switch) |
on/off |
| Dimmer | switchLevel |
brightness |
| AC / Climate | AirConditioner, Thermostat |
on/off, setpoint, mode |
| TV | Television, SmartMonitor |
on/off, volume, play/pause |
| Speaker | Speaker, NetworkAudio |
play/pause, volume |
| Appliance | Washer, Dryer, Dishwasher… |
on/off, state |
| Lock | lock |
lock / unlock |
| Sensor | MotionSensor, MultiFunctionalSensor, ContactSensor… |
read-only |
| Button | RemoteController (+ button) |
read-only |
| Hub | Hub / device type HUB |
read-only |
SmartThings makes long-lived personal auth awkward: Personal Access Tokens
created after Dec 2024 expire in 24 h, and the OAuth-In "SmartApp" consent
screen is currently unreliable (server-side An unexpected error occurred).
So this widget uses a CLI bridge (auth_mode: cli_bridge): it borrows the
login from the official SmartThings CLI. The CLI signs you in with a public
PKCE OAuth client that issues a refreshable token (scope controller:stCli,
which can read and control devices). setup.py copies that token into the
widget config, and the daemon then refreshes it itself — indefinitely — against
auth-global.api.smartthings.com using the CLI's public client id. No browser
consent, no 24 h expiry, no secret to guard.
A full OAuth-In flow is still included (
setup_oauth.py) as a fallback for if SmartThings fixes their consent screen.
- KDE Plasma 6
- Python 3.11+ (standard library only — no pip packages)
- Node.js + npm (only to install the SmartThings CLI, one-time)
- A SmartThings account with devices
git clone https://github.com/Gu7i/kde-smartthings-widget.git
cd kde-smartthings-widgetnpm install -g @smartthings/cli
smartthings login # opens your browser, sign in to your accountchmod +x install.sh
./install.shThe installer will:
- run
daemon/setup.py, which copies the CLI token into~/.config/smartthings-widget/config.jsonand lists your devices, - install and enable the
smartthings-daemonsystemd user service, - install the plasmoid (symlinked from the repo) and add it to your panel.
# 1. Bootstrap auth from the CLI login
cd daemon && python3 setup.py
# 2. Start the daemon
systemctl --user enable --now smartthings-daemon.service
# 3. Install the plasmoid
ln -s "$PWD/../plasmoid/smartthings-control" \
~/.local/share/plasma/plasmoids/org.kde.smartthings-control
# 4. Add it: right-click panel → Add widgets → "SmartThings Control"./uninstall.sh┌─────────────────────────────────────┐
│ KDE Panel │
│ ┌──────────────────────────────┐ │
│ │ Plasmoid (QML) │ │
│ │ GET /devices: 4s open/30s │ │
│ │ POST /devices/{id}/<action> │ │
│ └──────────────┬───────────────┘ │
└─────────────────┼───────────────────┘
│ HTTP 127.0.0.1:7182
┌─────────────────┼───────────────────┐
│ smartthings-daemon (Python, stdlib) │
│ ┌──────────────┴───────────────┐ │
│ │ smartthings_service.py │ │
│ │ sweeps the cloud every 20 s │ │
│ │ caches state, optimistic │ │
│ │ patch + reconcile on command │ │
│ └──────────────┬───────────────┘ │
└─────────────────┼───────────────────┘
│ HTTPS (SmartThings API v1)
SmartThings Cloud
SmartThings has no simple client WebSocket, so state is kept fresh by polling. Optimistic UI keeps taps feeling instant.
| Method | Path | Description |
|---|---|---|
| GET | /devices |
List all devices with state, room and local name |
| GET | /devices/{id} |
One device |
| GET | /status |
Daemon health + device count |
| GET | /auth/status |
Auth state |
| POST | /devices/{id}/on · /off · /toggle |
Switch (body {"outlet": 0} for multi) |
| POST | /devices/{id}/brightness |
{"value": 75} |
| POST | /devices/{id}/volume |
{"value": 30} (TV / speaker) |
| POST | /devices/{id}/play · /pause · /stop |
Media playback |
| POST | /devices/{id}/mode |
{"value": "cool"} (AC) |
| POST | /devices/{id}/setpoint |
{"value": 22} (AC) |
| POST | /devices/{id}/lock · /unlock |
Door lock |
| POST | /refresh |
Force a cloud sweep now; returns when it lands |
| GET | /aliases |
Local names, keyed by device id / {id}_{outlet} |
| POST | /alias |
Local rename {"key","name"} — empty name restores the original |
| POST | /channel_name |
Legacy channel rename {"device_id","outlet","name"} |
~/.config/smartthings-widget/config.json (mode 600), written by setup.py:
{
"auth_mode": "cli_bridge",
"cli_client_id": "…",
"refresh_url": "https://auth-global.api.smartthings.com/oauth/token",
"access_token": "…",
"refresh_token": "…",
"token_expires": "…"
}Every device (and every channel of a multi-switch) can be renamed from the
widget: hover the card and click the pencil. The new name lives only in
~/.config/smartthings-widget/aliases.json — nothing is renamed in the
SmartThings cloud, so the app on your phone keeps the original name. Clearing
the field (or the undo button) restores it. The older
channel_names.json is imported automatically on first run.
systemctl --user status smartthings-daemon
systemctl --user restart smartthings-daemon
journalctl --user -u smartthings-daemon -f
curl http://127.0.0.1:7182/devices | python3 -m json.tool
# Re-bootstrap auth (e.g. after a very long downtime)
cd daemon && python3 setup.pysetup.pysays it can't find CLI credentials — runsmartthings loginfirst; it must succeed in a browser.- Daemon shows 401 / no devices — the borrowed refresh token expired (only
happens after ~29 days of the daemon never running). Just
smartthings loginagain and re-runpython3 setup.py. - Widget shows "DAEMON OFFLINE" — check
systemctl --user status smartthings-daemonand the journal. - Tabs/UI didn't update after an edit — Plasma caches QML:
systemctl --user restart plasma-plasmashell.
sudo pacman -S python-pyqt6 # or your distro's PyQt6 package
python3 systray/systray.py