A lightweight cross-platform agent that bridges your PC with Home Assistant via MQTT.
PC Bridge runs on your PC and connects to Home Assistant over MQTT. It exposes your PC as a fully controllable device - detect games, send commands, get notifications, and automate everything.
| Feature | Description |
|---|---|
| Game Detection | Monitors running processes and reports current game |
| Game Catalog | Exposes all configured games as a sensor for dynamic dashboards |
| Idle Tracking | Reports last user input time |
| Power Events | Detects sleep/wake/display state instantly via OS events |
| System Sensors | CPU, memory, battery, active window (native APIs) |
| GPU Sensor | GPU utilization percentage (PDH on Windows, sysfs/nvidia-smi on Linux) |
| HWiNFO Sensors | Hardware monitoring via HWiNFO64 shared memory: GPU/CPU power, temps, clocks, fan RPMs, VRM, framerate (Windows only) |
| Network Sensor | Network throughput (bytes/sec per direction) |
| Disk Sensor | Disk usage for configured paths |
| Uptime Sensor | System uptime in seconds |
| Audio Control | Volume, mute, media keys via Home Assistant |
| Discord | Join/leave voice channel commands |
| Display Wake | Wakes display after WoL, dismisses screensaver |
| Remote Commands | Lock, hibernate, restart, shutdown, sleep, screensaver |
| Notifications | Native Windows toast notifications from Home Assistant |
| Steam Updates | steam_updating (on/off) from .acf files, plus the names of games currently downloading/updating |
| Auto-Update | Signed updates (minisign + anti-rollback) with stable/beta/disabled channels |
| Bridge Info | Publishes version, OS, arch, and enabled features on connect |
| Hot-Reload | Feature toggles, game mappings, and per-sensor poll intervals apply live, no restart |
| Settings Window | Native --ui window (egui) for config; launching the app while it's running opens it |
| System Tray | Toggleable tray icon with Open Settings / Quit (Windows) |
| Command Permissions | allow_global_launch (default on) and allow_global_close (default off) gate reaching beyond configured games |
| First-Run Wizard | Settings window on first run (terminal wizard as headless fallback) |
| Platform | Status |
|---|---|
| Windows 10/11 | Full support |
| Linux (X11) | Full support (bundled x11rb, no external tools needed) |
| Linux (Wayland) | Idle/active-window/display via bundled wlroots (ext-idle-notify) and GNOME/KDE D-Bus, no external tools |
| macOS | Build supported, limited features |
Grab the latest release from GitHub Releases:
| Platform | Binary |
|---|---|
| Windows | pc-bridge-windows.exe |
| Linux | pc-bridge-linux |
- Run the binary - the native settings window opens (a terminal wizard is used
only on a headless host) to guide you through:
- MQTT broker connection
- Device name
- Feature selection (all opt-in)
- Configuration is saved to
userConfig.json - PC Bridge connects and auto-discovers with Home Assistant
After setup the agent runs headless. To reopen the settings window later, launch the
app again (it opens the window instead of starting a second agent), use the tray
icon's Open Settings, or run it with --ui.
Edit userConfig.json. It lives in the per-user config directory
(%APPDATA%\pc-bridge\ on Windows, ~/.config/pc-bridge/ on Linux and macOS);
a copy next to the executable is the legacy location and is migrated on startup.
Set PC_BRIDGE_CONFIG_DIR to override the directory entirely (used by the test
kit, and handy for portable installs).
{
"device_name": "my-pc",
"mqtt": {
"broker": "tcp://homeassistant.local:1883",
"user": "mqtt_user",
"pass": "mqtt_pass"
},
"features": {
"running_game": true,
"game_catalog": true,
"launch_game": true,
"steam_library": true,
"idle_tracking": true,
"sleep_wake": true,
"display_state": true,
"display_attached": false,
"cmd_shutdown": true,
"cmd_restart": true,
"cmd_sleep": true,
"cmd_lock": true,
"notifications": true,
"cpu_sensor": true,
"memory_sensor": true,
"active_window": true,
"volume": true,
"media_controls": true,
"steam_updates": false,
"discord": false,
"gpu_sensor": false,
"hwinfo_sensor": false,
"network_sensor": false,
"disk_sensor": false,
"uptime_sensor": false
},
"update_channel": "stable",
"disk_sensor_paths": ["C:\\"],
"intervals": {
"game_sensor": 5,
"last_active": 10
},
"games": {
"bf6": "battlefield_6",
"FortniteClient-Win64-Shipping": "fortnite",
"MarvelRivals_Shipping": "marvel_rivals"
},
"custom_sensors_enabled": false,
"custom_commands_enabled": false,
"custom_command_privileges_allowed": false,
"allow_raw_commands": false,
"custom_sensors": [],
"custom_commands": []
}Every feature is an opt-in boolean in the features object of userConfig.json.
The easiest way to manage them is the pc-bridge settings app (the tray/window UI): it lists every feature with its exact config key, the Home Assistant entity it reports as, its prerequisites, and a short "how it works" note, and writes the config for you. That in-app catalog is the source of truth for the full flag list.
Defaults: the power and display controls (sleep/wake, shutdown, restart, sleep, lock, logoff, monitor on/off) are on; everything else (game detection, hardware and system sensors, audio, Discord, notifications, HWiNFO, etc.) is off until you enable it.
Flags are granular, e.g. game detection is running_game / game_catalog /
steam_library / launch_game / close_game, and audio is volume /
media_controls. The older coarse keys (game_detection, power_events,
system_sensors, audio_control) are still accepted and are migrated to the
granular flags automatically on first load.
| Setting | Default | Description |
|---|---|---|
update_channel |
"stable" |
Update channel: "stable", "beta", or "disabled" |
disk_sensor_paths |
[] |
Paths to check for disk usage (e.g. ["C:\\", "D:\\"] or ["/", "/home"]) |
show_tray_icon |
true |
Show the Windows system tray icon (Open Settings / Quit); toggles live |
allow_global_launch |
true |
Let launch commands start titles that aren't in your configured games |
allow_global_close |
false |
Let close/kill commands target processes that aren't configured games |
allow_raw_commands |
false |
Run arbitrary exe:/lnk:/url: payloads not matching a configured game |
intervals |
per-sensor | Poll intervals (seconds) per sensor: cpu, memory, gpu, network, disk, ... |
Note: Missing fields are automatically added with their defaults when upgrading.
When hwinfo_sensor: true, pc-bridge reads ~20 hardware sensors from HWiNFO64's shared memory and exposes them as Home Assistant entities. Entities published:
| Category | Sensors |
|---|---|
| GPU | gpu_power, gpu_temp, gpu_hotspot_temp, gpu_memory_temp, gpu_core_clock, gpu_memory_clock, gpu_core_load, gpu_vram_usage_pct, gpu_fan_rpm |
| CPU | cpu_package_power, cpu_soc_power, cpu_package_temp, cpu_effective_clock, cpu_total_usage |
| Mainboard | vrm_temp, case_fan_cpu, case_fan_cpu_opt, case_fan_system_1, case_fan_system_2 |
| Game | framerate |
- Install HWiNFO64 (free).
- Open HWiNFO Settings → check Shared Memory Support. Restart HWiNFO.
- Set
"hwinfo_sensor": trueinuserConfig.jsonand restart pc-bridge.
HWiNFO must be running for the sensors to publish. Entities become unavailable in HA when HWiNFO closes; they restore automatically when it reopens.
Sensors are matched by hardcoded name patterns. Coverage is best on the hardware the rules were written against: several CPU rules require an AMD Ryzen sensor name, and the case-fan rules follow Gigabyte/ITE label conventions, so on other boards some sensors will not match. Unmatched keys are logged at debug level.
Publishes are throttled per-sensor: power changes by 5W, temperatures by 0.5°C, clocks by 50MHz, with a 30-second heartbeat so HA always has a recent value. The producer task only reads 8 bytes of shared memory between updates, so the CPU cost is negligible.
With the Steam feature on, sensor.<device>_steam_updating reports on/off from
Steam's .acf files (no setup, no ports), and its attributes list the names of the
games currently downloading or updating. A live download percentage is not exposed:
Steam only makes that available in-process (via its CEF debug port or DLL injection),
both of which are security/stability tradeoffs pc-bridge deliberately avoids.
The games object maps process names to game IDs:
- Key: Part of the process name to match (case-insensitive)
- Value: The game ID reported to Home Assistant (string or object)
Full format (used by Steam auto-discovery):
{
"games": {
"bg3": {
"game_id": "baldurs_gate_3",
"app_id": 1086940,
"name": "Baldur's Gate 3",
"exposed": true
}
}
}| Field | Required | Description |
|---|---|---|
game_id |
Yes | Identifier reported to Home Assistant |
app_id |
No | Steam App ID (set automatically by Steam discovery) |
name |
No | Display name (defaults to title-cased game_id) |
exposed |
No | Whether to include in the game catalog sensor (default: true) |
auto_discovered |
No | Set automatically by Steam discovery |
Custom sensors and commands are configured at the root level of userConfig.json (not inside features).
Custom features are disabled by default and require explicit opt-in:
| Setting | Default | Purpose |
|---|---|---|
custom_sensors_enabled |
false |
Enable custom sensor polling |
custom_commands_enabled |
false |
Enable custom command execution |
custom_command_privileges_allowed |
false |
Allow commands marked admin: true |
allow_raw_commands |
false |
Allow arbitrary MQTT payloads to be executed as shell commands |
⚠️ allow_raw_commands: Whenfalse(default), only predefined commands (Shutdown, Sleep, Wake, etc.) and configured custom commands can be executed. Unknown command topics with a non-empty payload are silently dropped. Set totrueonly if you need to send ad-hoc shell commands via MQTT - this is a security risk if your MQTT broker is not properly secured.
Monitor anything - GPU temperature, service status, disk space:
{
"custom_sensors_enabled": true,
"custom_sensors": [
{
"name": "gpu_temp",
"type": "powershell",
"script": "(Get-CimInstance -Namespace root/cimv2 -ClassName Win32_PerfFormattedData_GPUPerformanceCounters_GPUEngine | Where-Object Name -like '*engtype_3D*' | Select-Object -First 1).UtilizationPercentage",
"unit": "°C",
"icon": "mdi:thermometer",
"interval_seconds": 30
},
{
"name": "disk_free_c",
"type": "powershell",
"script": "[math]::Round((Get-PSDrive C).Free / 1GB, 1)",
"unit": "GB",
"icon": "mdi:harddisk",
"interval_seconds": 300
},
{
"name": "is_vpn_connected",
"type": "process_exists",
"process_name": "openvpn"
},
{
"name": "hostname",
"type": "registry",
"registry_path": "HKLM\\SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ComputerName",
"registry_value": "ComputerName"
}
]
}Sensor Types:
| Type | Description | Required Fields |
|---|---|---|
powershell |
Run PowerShell, use stdout as value | script |
process_exists |
Returns "true"/"false" | process_name |
file_contents |
Read file contents | file_path |
registry |
Read registry value (Windows) | registry_path, registry_value |
Execute custom actions from Home Assistant:
{
"custom_commands_enabled": true,
"custom_command_privileges_allowed": true,
"allow_raw_commands": false,
"custom_commands": [
{
"name": "flush_dns",
"type": "powershell",
"script": "ipconfig /flushdns",
"icon": "mdi:dns",
"admin": true
},
{
"name": "clear_temp",
"type": "powershell",
"script": "Remove-Item $env:TEMP\\* -Recurse -Force -ErrorAction SilentlyContinue",
"icon": "mdi:broom"
},
{
"name": "open_calculator",
"type": "executable",
"executable": "calc.exe"
}
]
}Command Types:
| Type | Description | Required Fields |
|---|---|---|
powershell |
Run PowerShell script | script |
executable |
Run an executable | executable, optional args |
shell |
Run via cmd.exe | shell_command |
powershell and shell are separate types because they use different interpreters. Use powershell for PowerShell cmdlets and scripts. Use shell for cmd.exe commands (batch scripts, .bat files, dir, copy, etc.) that may not work in PowerShell. Admin powershell commands use base64-encoded -EncodedCommand to prevent injection. Admin shell commands are elevated via Start-Process cmd -Verb RunAs.
Running script files: To run
.ps1files, use thepowershelltype with"script": "& 'C:\\path\\script.ps1'". Theexecutabletype works for.bat/.cmdfiles directly, but.ps1files require PowerShell's execution policy handling.
Security:
- Commands with
admin: truerequirecustom_command_privileges_allowed: true - Admin commands run via
Start-Process -Verb RunAs(UAC prompt may appear) - Non-admin commands run in current user context
PC Bridge can display Windows toast notifications sent from Home Assistant. Uses native WinRT APIs for ~10ms latency (no PowerShell overhead).
After PC Bridge connects, a notify.send_message entity is auto-discovered:
# In HA automations, scripts, etc.
action: notify.send_message
metadata: {}
data:
message: Motion detected at front door!
title: Security Alert
target:
entity_id: notify.my_pc_notificationSend JSON to the notify topic for full control:
{"title": "Alert Title", "message": "Notification body text"}Or just plain text (uses "Home Assistant" as default title):
Your plain text message here
You can also publish directly to the MQTT topic:
Topic: pc-bridge/notifications/{device_name}
Payload: {"title": "My Title", "message": "My message"}
Doorbell notification:
automation:
- alias: "Doorbell: Notify PC"
trigger:
- platform: state
entity_id: binary_sensor.doorbell
to: "on"
action:
- action: notify.send_message
data:
title: "Doorbell"
message: "Someone is at the door"
target:
entity_id: notify.my_pc_notificationWasher done notification:
automation:
- alias: "Laundry: Notify when done"
trigger:
- platform: state
entity_id: sensor.washer_status
to: "complete"
action:
- action: notify.send_message
data:
title: "Laundry"
message: "Washer cycle complete!"
target:
entity_id: notify.my_pc_notificationGame suggestion notification:
script:
suggest_game:
sequence:
- action: notify.send_message
data:
title: "Game Time?"
message: "How about playing {{ states('sensor.suggested_game') }}?"
target:
entity_id: notify.my_pc_notificationSend commands via MQTT button topics:
| Button | Description |
|---|---|
Screensaver |
Activate screensaver |
Wake |
Wake display, dismiss screensaver |
Lock |
Lock workstation |
Shutdown |
Power off the PC |
Sleep |
Put PC to sleep |
Hibernate |
Hibernate the PC |
Restart |
Restart the PC |
| Button | Description |
|---|---|
MediaPlayPause |
Play/pause media |
MediaNext |
Next track |
MediaPrevious |
Previous track |
MediaStop |
Stop media |
VolumeMute |
Toggle mute |
VolumeSet |
Set volume (payload: 0-100). Exposed as number.<device>_volume, not a button |
The Launch button accepts special payloads:
| Payload | Description |
|---|---|
steam:1234 |
Launch Steam game by App ID |
update:1234 |
Validate/update an installed Steam game without launching it (alias: validate:1234; both map to steam://validate/<id>) |
epic:GameName |
Launch Epic game |
exe:C:\path\to.exe |
Run executable directly |
lnk:C:\path\to.lnk |
Run shortcut file |
close:processname |
Close process gracefully |
Paths with spaces work automatically -- no manual quoting needed (e.g., exe:C:\Program Files\Game\game.exe). Shell metacharacters (; | & $ ` 'and on Windows also ") are rejected to prevent command injection.
Note: The
Launchbutton requires you to define actions in Home Assistant that send the appropriate payload. Unlike custom commands (which are self-contained), Launch is a generic endpoint that executes whatever payload you send it.
Control Discord voice channels directly from Home Assistant.
| Button | Description |
|---|---|
DiscordJoin |
Join a voice channel (requires payload) |
DiscordLeaveChannel |
Leave current voice channel (no payload) |
DiscordJoin uses the Launch system under the hood - send a url: payload with a Discord deep link:
url:discord://discord.com/channels/SERVER_ID/CHANNEL_ID
DiscordLeaveChannel simulates a keyboard shortcut (default: Ctrl+F6, Discord's built-in "Disconnect from Voice Channel" hotkey). Customize it in userConfig.json:
{
"discord_keybind": "ctrl+f6"
}Example HA scripts:
# Join a specific voice channel
discord_join_gaming:
alias: "Discord: Join Gaming Channel"
sequence:
- action: mqtt.publish
data:
topic: homeassistant/button/my-pc/DiscordJoin/action
payload: "url:discord://discord.com/channels/123456789/987654321"
# Leave any voice channel
discord_leave:
alias: "Discord: Leave Channel"
sequence:
- action: mqtt.publish
data:
topic: homeassistant/button/my-pc/DiscordLeaveChannel/action
payload: "PRESS"Tip: You can find the server and channel IDs in Discord by enabling Developer Mode (Settings → App Settings → Advanced → Developer Mode), then right-clicking a server or channel and selecting "Copy ID".
PC Bridge auto-discovers via MQTT. After connecting, you'll get:
Sensors:
sensor.<device>_runninggames- Current game (or "none") - instant via process eventssensor.<device>_sleep_state- "awake" or "sleeping" - instant via OS power eventssensor.<device>_lastactive- ISO timestamp of last input (polled 10s)sensor.<device>_screensaver- "on" or "off" - instant via WMI eventssensor.<device>_display- "on" or "off" - instant via OS power eventssensor.<device>_cpu_usage- CPU usage percentage (polled 10s)sensor.<device>_memory_usage- Memory usage percentage (polled 10s)sensor.<device>_battery_level- Battery percentage - instant via OS power eventssensor.<device>_battery_charging- "true" or "false" - instant via OS power eventssensor.<device>_active_window- Current foreground window title - instant via SetWinEventHooksensor.<device>_game_catalog- Number of exposed games, with full game list as attributes (retained)sensor.<device>_steam_updating- "on"/"off" with game list - instant via filesystem watchersensor.<device>_volume_level- System volume percentagesensor.<device>_gpu_usage- GPU utilization percentage (polled)sensor.<device>_network_throughput- Network throughput with rx/tx attributes (polled)sensor.<device>_disk_usage- Highest disk usage % with per-path attributes (polled)sensor.<device>_system_uptime- System uptime in seconds (polled 60s)sensor.<device>_bridge_info- Agent version, OS, arch, enabled features (on connect)sensor.<device>_bridge_health- Agent uptime in seconds (heartbeat, ~60s), with version, per-task states, and agent memory (MB) as attributes. Always published regardless of which sensor features are enabled.sensor.<device>_<custom>- Any custom sensors you define
Buttons:
button.<device>_screensaverbutton.<device>_wakebutton.<device>_lockbutton.<device>_shutdownbutton.<device>_sleepbutton.<device>_hibernatebutton.<device>_restartbutton.<device>_launchbutton.<device>_refreshsteamgames(requiresgame_detection)button.<device>_mediaplaypausebutton.<device>_medianextbutton.<device>_mediapreviousbutton.<device>_mediastopbutton.<device>_volumemutenumber.<device>_volume- Set volume 0-100 (requiresvolume)button.<device>_discordjoin(requiresdiscord)button.<device>_discordleavechannel(requiresdiscord)button.<device>_<custom>- Any custom commands you define
Notifications:
notify.<device>_notification- Send toast notifications to your PC
Where <device> is your configured device_name with dashes replaced by underscores.
The device availability topic (online/offline) is a true readiness beacon: the
agent publishes online only after it has subscribed to every command topic on each
broker connection, so online means "subscribed and ready to execute commands". It
publishes offline before suspending (during pre-suspend) and via MQTT Last Will if
it crashes. When automating a launch, gate it on availability being online plus a
live bridge_health heartbeat, rather than firing a command blindly.
Idle time, active window, display power, and display wake are handled by bundled
pure-Rust backends (x11rb on X11; ext-idle-notify / wlr and GNOME/KDE D-Bus on
Wayland), so no external tools are required for those anymore. A few things
still shell out to system utilities that are usually already installed:
| Package | Purpose |
|---|---|
pactl |
Audio control (ships with PulseAudio/PipeWire) |
gdbus |
Sleep/wake detection (ships with GLib) |
playerctl |
Now-playing / media info (optional) |
xdotool / xprintidle |
Optional fallbacks only if the bundled X11 backend can't attach |
Building the --ui settings window on Linux needs GTK dev headers for the file
dialog (libgtk-3-dev / gtk3-devel / gtk3); the headless agent does not.
# Create service
sc create PCBridge binPath= "C:\path\to\pc-bridge.exe"
sc config PCBridge start= auto
sc start PCBridgeCreate /etc/systemd/system/pc-bridge.service:
[Unit]
Description=PC Bridge - Home Assistant Integration
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/pc-bridge
WorkingDirectory=/usr/local/bin
Restart=always
RestartSec=10
User=your-username
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload
sudo systemctl enable pc-bridge
sudo systemctl start pc-bridgeAll system sensors use native APIs for minimal overhead:
| Metric | Value |
|---|---|
| Binary size | ~1.3 MB |
| Memory usage | ~2.5 MB |
| CPU usage | < 1% |
Native API Performance (Windows):
GetSystemTimes(CPU): ~1μsGlobalMemoryStatusEx(memory): ~1μsGetSystemPowerStatus(battery): ~1μsGetForegroundWindow(active window): ~1μsIAudioEndpointVolume(volume): ~50μs
Custom Sensors Impact:
- Each PowerShell sensor: ~10-50ms execution per poll
- Process check sensor: ~1ms (native API)
- Registry read: ~0.1ms (native API)
- Memory: +~100 bytes per sensor for state tracking
- Recommended: Keep intervals ≥30s for PowerShell sensors
# Install Rust (https://rustup.rs)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Clone and build
git clone https://github.com/dank0i/pc-bridge.git
cd pc-bridge
# Windows (native)
cargo build --release
# Linux (native)
cargo build --release
# Cross-compile Windows from Linux/macOS
rustup target add x86_64-pc-windows-gnu
cargo build --release --target x86_64-pc-windows-gnuThis project is licensed under the MIT License.