- Python 96.2%
- Shell 3.8%
|
|
||
|---|---|---|
| systemd | ||
| vendor | ||
| .gitignore | ||
| CLAUDE.md | ||
| config.example.toml | ||
| install.sh | ||
| LICENSE | ||
| README.md | ||
| steamdeck2mqtt.py | ||
steamdeck2mqtt
A battery-efficient, event-driven bridge that pushes live Steam Deck status to Home Assistant over MQTT.
- Survives SteamOS updates. It lives under
$HOMEand never touches the read-only rootfs, so there's nosteamos-readonly disableto redo after each update (the thing that breaksgo-hass-agent). - Easy on the battery. One mostly-idle main loop, driven by D-Bus and
udevevents instead of polling, so changes reach HA in about a second. - Knows what you're playing, everywhere. Detects the running game in Gaming and Desktop Mode, reports Steam Remote Play and Moonlight streams, and tells native from Proton. It also makes a best-effort attempt to pull the console and ROM out of emulator front-ends.
Documentation
Everything appears as one Home Assistant device, Steam Deck. The ID column is the MQTT object
id; HA exposes it as e.g. sensor.steam_deck_battery. Whole groups can be toggled off in
config.toml under [entities].
Battery & power
Live on change; power, temperatures, time remaining and the charge limit refresh every ~90 s.
| Entity | ID | Type | Notes |
|---|---|---|---|
| Battery | battery |
sensor % |
|
| Battery charging | battery_charging |
binary | |
| AC power | ac_power |
binary | charger connected |
| Battery power | battery_power |
sensor W |
charge/discharge magnitude |
| Battery state | battery_state |
sensor | charging, discharging, full, pending_charge, etc. |
| Battery temperature | battery_temperature |
sensor °C |
|
| Battery charge limit | battery_charge_limit |
sensor % |
100 = no limit |
| Battery time to full | battery_time_to_full |
sensor min |
while charging |
| Battery time to empty | battery_time_to_empty |
sensor min |
while discharging |
Dock & display
Live on change.
| Entity | ID | Type | Notes |
|---|---|---|---|
| External display | external_display |
binary | DP/HDMI connected |
| USB host | usb_host |
binary | dock or hub attached |
| Docked | docked |
binary | external display or USB host |
There's no real "docked" bit on SteamOS, and a wall charger gives neither a display nor a USB host, so the raw signals are exposed separately. Build your own meaning in HA if you like.
Session
Suspended is instant; Wi-Fi values refresh every ~90 s.
| Entity | ID | Type | Notes |
|---|---|---|---|
| Suspended | suspended |
binary | |
| Wi-Fi SSID | wifi_ssid |
sensor | empty when not connected |
| Wi-Fi signal | wifi_signal |
sensor dBm |
Game
Instant in Gaming Mode; otherwise polled every 5 s on AC, 15 s on battery.
| Entity | ID | Type | Notes |
|---|---|---|---|
| Game | game |
sensor | running title, or emulated ROM; empty when no game |
| Game state | game_state |
sensor (enum) | in-game / steam-ui / none / desktop-mode |
| Gaming mode | gaming_mode |
binary | on in Gaming Mode, off in Desktop Mode |
| Game platform | game_platform |
sensor | Native / Proton / emulated (e.g. SNES) / Remote Play / Moonlight |
| Game App ID | game_app_id |
sensor | Steam AppID |
The neat part is how far detection reaches. Anything launched through Steam shows up in both
Gaming Mode and Desktop Mode, with the right Game platform attached: Native, Proton, the
emulated console behind a front-end (like ES-DE or RetroArch) with Game showing the inner ROM,
or Remote Play / Moonlight when you're streaming the Deck from another machine. Apps started
outside Steam aren't tracked.
Library & downloads
Updates live during a download (installed games only).
| Entity | ID | Type | Notes |
|---|---|---|---|
| Games installed | games_installed |
sensor | across internal + SD |
| Updates queued | updates_queued |
sensor | |
| Downloading | downloading |
binary | |
| Downloading game | downloading_game |
sensor | |
| Download progress | download_progress |
sensor % |
|
| Download paused | download_paused |
binary |
System
Re-checked every 5 s on AC, 15 s on battery, but only published when a value changes (mostly static, so usually nothing is sent).
| Entity | ID | Type | Notes |
|---|---|---|---|
| SteamOS version | steamos_version |
sensor | |
| Kernel version | kernel_version |
sensor | |
| Steam version | steam_version |
sensor | |
| BIOS version | bios_version |
sensor | |
| Model | model |
sensor | OLED or LCD |
| Uptime | uptime |
sensor (timestamp) | shown as relative in HA |
| Storage free / total / used | storage_free / storage_total / storage_used |
sensor GB |
internal /home |
| SD Card | sd_card |
binary | card inserted |
| SD Card free / total / used | sd_card_free / sd_card_total / sd_card_used |
sensor GB |
unknown when no card |
Controls
Read-only; refreshed every 5 s on AC, 15 s on battery.
| Entity | ID | Type | Notes |
|---|---|---|---|
| Brightness | brightness |
sensor % |
|
| Volume | volume |
sensor % |
|
| Volume muted | volume_muted |
binary | |
| Microphone | microphone |
sensor % |
level |
| Wi-Fi | wifi |
binary | radio on/off |
| Bluetooth | bluetooth |
binary | radio on/off |
| Airplane mode | airplane_mode |
binary | |
| Bluetooth devices | bluetooth_devices |
sensor | connected count |
| Bluetooth device names | bluetooth_device_names |
sensor | connected names |
Performance
Refreshed every 5 s on AC, 15 s on battery.
| Entity | ID | Type | Notes |
|---|---|---|---|
| FPS | fps |
sensor fps |
Gaming Mode only |
| GPU usage | gpu_usage |
sensor % |
|
| GPU temperature | gpu_temperature |
sensor °C |
|
| GPU power | gpu_power |
sensor W |
|
| CPU usage | cpu_usage |
sensor % |
|
| CPU temperature | cpu_temperature |
sensor °C |
|
| RAM used | ram_used |
sensor % |
|
| Fan speed | fan_speed |
sensor rpm |
Use cases
A few automations these entities make easy:
- Cinema mode for the couch. When
Gamebecomes non-empty whileDockedis on, dim the room lights and flip the TV to the Deck's input, then bring them back when the game closes. - Battery care. Cut a charging smart plug when
Batteryreaches yourBattery charge limit, or ping your phone whenBatteryruns low whileAC poweris off. - Download babysitting. Get a notification (or turn on a lamp) the moment
Downloadingflips off, so you know a big install finished without watchingDownload progress. - Now-playing presence. Set a desk lamp colour or a do-not-disturb flag while
Game stateis in-game, and clear it in the Steam UI or Desktop Mode. - Heat watch. If
CPU temperature,GPU temperatureorFan speedclimbs during a session, switch on a desk fan via a smart plug and nudge you. - Wake the host. When
Game platformreadsMoonlightorRemote Playyou're streaming from another machine, so power on that gaming PC or set its scene. - Update nudge. When
Updates queuedgoes above zero, remind yourself to dock and update overnight.
How it works
Battery and power come from UPower, suspend from logind, dock and display hotplug from udev,
and the running game from gamescope. These all arrive as events, so there's nothing to poll. The
only timers are a ~90 s heartbeat (keepalive plus a refresh of slow-moving values) and a game
poll every 5 s on AC, 15 s on battery, for the few things Steam doesn't signal.
It publishes a final snapshot before the Deck suspends and refreshes on resume, without blocking sleep. A Last-Will marks the device offline if the Deck drops; it re-announces itself and re-publishes everything when it or Home Assistant reconnects. Idle cost is roughly one or two CPU wakeups per minute.
Install
cd ~/dev/steamdeck2mqtt
./install.sh # vendors paho, writes config, installs+enables the user service
$EDITOR ~/.config/steamdeck2mqtt/config.toml # set broker host / username / password
python3 steamdeck2mqtt.py --dump # sanity-check: prints every signal it can read, no MQTT
systemctl --user start steamdeck2mqtt
journalctl --user -u steamdeck2mqtt -f
A Steam Deck device appears under Settings > Devices & Services > Devices.
Uninstall
./install.sh uninstall # stops the service AND removes the retained device from HA