# Home Assistant Bridge > One shared Home Assistant connection for your machine, served to local apps over a single socket. # Overview Source: https://ha-bridge.timmo.dev/ Home Assistant Bridge keeps one [Home Assistant](https://www.home-assistant.io) WebSocket connection open per machine and serves it to local apps over a single Unix socket. Status bars, keyboard shortcuts, scripts and your own apps all share that one connection, instead of each opening their own. ```bash # Toggle light.desk ha-bridge light toggle desk # Print sensor.office_temperature now and on every change, as JSON for a status bar ha-bridge watch entity sensor.office_temperature --bar-json ``` ## How it fits together - `ha-bridge serve` runs as a systemd user service. It connects to Home Assistant, caches every entity's state and keeps it current. - Every other command is a small client. It connects to the bridge socket, makes one request and exits, or streams updates for watchers. - Apps written with [Effect](https://effect.website) can talk to the socket directly with [`@timmo001/effect-ha-bridge`](/libraries/). ## Get started | Page | Why | | --- | --- | | [Install](/install/) | Install the Arch package or a release build | | [Configuration](/configuration/) | Set your Home Assistant URL and access token | | [Running the bridge](/running/) | The user service, logs and upgrades | | [Actions](/using/actions/) | Control lights, switches, covers, climate, helpers and satellites | | [Watching entities](/using/watching/) | Stream entity state into scripts and status bars | | [Commands](/reference/commands/) | Every command, alias and flag | | [Libraries](/libraries/) | Use the bridge or Home Assistant from your own Effect app | | [Migrating from Go Automate](/from-go-automate/) | Command, service and config changes | ## Machine-readable docs | URL | Use | | --- | --- | | https://ha-bridge.timmo.dev/llms.txt | Compact page index | | https://ha-bridge.timmo.dev/llms-full.txt | Full docs bundle | | https://ha-bridge.timmo.dev/mcp | Hosted MCP (search, page and navigation) | | `/{route}.md` | One page as Markdown | --- # Configuration Source: https://ha-bridge.timmo.dev/configuration The bridge needs your Home Assistant URL and a long-lived access token. Only `ha-bridge serve` reads them; every other command talks to the bridge socket. ## Set up Run setup once in a terminal: ```bash ha-bridge setup ``` It asks for your URL and token, then saves them. The token isn't shown as you type or paste it. Run it again to change either value; it offers your saved URL as the default. If the bridge service is already running, restart it so it uses the new values: ```bash systemctl --user restart ha-bridge.service ``` A service that started before you ran setup keeps retrying every 5 seconds, so it picks up a new config without a restart. ## Config file Setup writes `config.yml` in `$XDG_CONFIG_HOME/ha-bridge`, which is usually `~/.config/ha-bridge/config.yml`: ```yaml homeassistant: url: http://homeassistant.local:8123 token: your-long-lived-access-token ``` - `homeassistant.url`: your Home Assistant base URL, starting with `http://` or `https://`. The bridge connects over `ws://` or `wss://` to match. - `homeassistant.token`: a long-lived access token. The directory is only readable by you (`0700`) and the file by you (`0600`). The config is per user, so run setup as the same user that runs the bridge service. ### Go Automate config If `~/.config/ha-bridge/config.yml` doesn't exist, the bridge reads `~/.config/go-automate/config.yml` instead and copies it to the new location. See [Migrating from Go Automate](/from-go-automate/). ## Create a long-lived access token 1. In Home Assistant, open your profile. 2. Go to the **Security** tab and scroll to **Long-lived access tokens**. 3. Select **Create token**, name it (for example `ha-bridge`) and copy the value. Home Assistant only shows the token once, so paste it straight into `ha-bridge setup`. ## Socket path Commands find the bridge socket in this order: 1. `--socket ` 2. `$HA_BRIDGE_SOCK` 3. `$XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock` 4. `$TMPDIR/ha-bridge-$USER/ha-bridge.sock`, falling back to `/tmp` and `default` `ha-bridge serve` uses the same order to decide where to listen, so the defaults always match. --- # Migrating from Go Automate Source: https://ha-bridge.timmo.dev/from-go-automate Home Assistant Bridge replaces [Go Automate](https://github.com/timmo001/go-automate). The commands do the same things, but every command now goes through the bridge, so the bridge service must be running for actions as well as watchers. ## What happened to Go Automate Go Automate was a Go CLI for triggering Home Assistant from key bindings, which later grew a socket bridge so status bars could share one connection for watching entities. Actions still opened their own connection each time, and anything else that needed Home Assistant called it directly with its own copy of the token. Home Assistant Bridge is a rewrite in TypeScript with Effect, built around the bridge. One service owns the connection, and actions, reads and watches all go through its socket. Other apps can use the same connection through the [client library](/libraries/) instead of shelling out. Go Automate is archived and no longer maintained, and its docs site now redirects here. ## Commands Drop `go-automate ha` from the front, and `bridge` from the bridge commands: | Go Automate | Home Assistant Bridge | | --- | --- | | `go-automate ha bridge serve` | `ha-bridge serve` | | `go-automate ha bridge watch entity ` | `ha-bridge watch entity ` | | `go-automate ha light toggle ` | `ha-bridge light toggle ` | | `go-automate ha cover watch ` | `ha-bridge cover watch ` | | `go-automate completion zsh` | `ha-bridge --completions zsh` | The domain commands, their aliases, arguments and flags are unchanged. `camera snapshot` and `setup` are new. See [Commands](/reference/commands/) for the full list. ## Service and socket | | Go Automate | Home Assistant Bridge | | --- | --- | --- | | User service | `go-automate-home-assistant-bridge.service` | `ha-bridge.service` | | Socket | `$XDG_RUNTIME_DIR/go-automate/home-assistant.sock` | `$XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock` | | Protocol | `get_entity` and `watch_entity` JSON requests | [Effect RPC](/reference/protocol/) | Scripts that spoke Go Automate's socket protocol directly need moving to the [new protocol](/reference/protocol/) or the [client library](/libraries/). ## Config You don't need to set anything up again. When `~/.config/ha-bridge/config.yml` doesn't exist, the bridge reads `~/.config/go-automate/config.yml` and copies it across. Go Automate asked for your URL and token the first time it ran. Home Assistant Bridge doesn't prompt on its own; run `ha-bridge setup` instead. ## Output Bar JSON has the same fields and key order, with two small differences: - A `null` target temperature or tilt position is left out of `climate watch` and `cover watch` text, as if the attribute were missing. - `<`, `>` and `&` are printed as they are, not as `\u003c`, `\u003e` and `\u0026`. JSON parsers read both the same way. ## Switching over 1. Stop and disable the old service: ```bash systemctl --user disable --now go-automate-home-assistant-bridge.service ``` 2. [Install](/install/) Home Assistant Bridge, then make sure `ha-bridge.service` is running. 3. Update your key bindings, bar modules and scripts to the new commands. 4. Remove Go Automate. --- # Install Source: https://ha-bridge.timmo.dev/install Home Assistant Bridge is a single Linux binary for x86_64 and aarch64. Every package includes the binary, the `ha-bridge.service` [user service](/running/) and shell completions for bash, zsh and fish. ## Arch Linux Packages are published to the unofficial `timmo` pacman repository. Add it before the other repository sections in `/etc/pacman.conf`: ```ini [timmo] SigLevel = PackageRequired DatabaseOptional TrustedOnly Server = https://packages.timmo.dev/$arch ``` Then install one of the two packages: | Package | Built from | | --- | --- | | `ha-bridge-bin` | The latest release | | `ha-bridge-git` | Every push to `main` | ```bash sudo pacman -Syu ha-bridge-bin ``` The packages conflict with each other, so installing one replaces the other. The install hook enables `ha-bridge.service` for every user's future logins. If you install with `sudo` from a running desktop session, it also starts the service for you. On upgrade it restarts the service only if it was already running, so a service you stopped stays stopped. ## Debian, Ubuntu and Fedora Each [GitHub release](https://github.com/timmo001/ha-bridge/releases) has `.deb` and `.rpm` packages: ```bash # Debian and Ubuntu sudo apt install ./ha-bridge__amd64.deb # Fedora sudo dnf install ./ha-bridge--1.x86_64.rpm ``` These install the user service but don't enable it. See [Running the bridge](/running/). ## Release archive Each release also has a `ha-bridge--linux-.tar.gz` archive with just the binary. Put it somewhere on your `PATH`: ```bash tar -xzf ha-bridge--linux-x86_64.tar.gz install -Dm755 ha-bridge ~/.local/bin/ha-bridge ``` Release assets come with a `SHA256SUMS` file and a Sigstore bundle. To check an asset was built by this repository's release workflow: ```bash gh attestation verify ha-bridge--linux-x86_64.tar.gz --repo timmo001/ha-bridge ``` ## Build from source You need [mise](https://mise.jdx.dev), which installs the pinned Bun and Node versions: ```bash git clone https://github.com/timmo001/ha-bridge.git cd ha-bridge mise install mise run build ``` The binary is written to `dist/ha-bridge`. ## Next steps - [Configure](/configuration/) your Home Assistant URL and token. - [Run the bridge](/running/) as a user service. --- # Libraries Source: https://ha-bridge.timmo.dev/libraries Home Assistant Bridge is built from two Effect v4 libraries, published to npm and JSR. Both work under Bun and Node. | Package | Use it to | | --- | --- | | [`@timmo001/effect-ha-bridge`](https://github.com/timmo001/ha-bridge/tree/main/packages/client) | Talk to a running bridge over its socket, sharing its connection | | [`@timmo001/effect-ha`](https://github.com/timmo001/ha-bridge/tree/main/packages/effect-ha) | Connect to Home Assistant yourself, with typed actions and schemas | Most local apps want the bridge client: they start instantly, share the bridge's cache and never hold a token. ## Bridge client ```bash bun add @timmo001/effect-ha-bridge @timmo001/effect-ha effect ``` `BridgeClient` has one method per [RPC](/reference/protocol/#rpcs): `GetEntity`, `WatchEntity`, `CallAction`, `GetConfig` and `CameraSnapshot`. `resolveSocketPath` finds the socket the same way the CLI does, and `getCalendarEvents` reads calendar events through `CallAction`. ```ts import { BunRuntime, BunServices } from "@effect/platform-bun"; import { Light } from "@timmo001/effect-ha"; import { BridgeClient, resolveSocketPath } from "@timmo001/effect-ha-bridge"; import { Effect, Option } from "effect"; const main = Effect.gen(function* () { const socketPath = yield* resolveSocketPath(Option.none()); yield* Effect.gen(function* () { const client = yield* BridgeClient; yield* client.CallAction(Light.toggle("light.desk")); }).pipe(Effect.provide(BridgeClient.layer(socketPath))); }); main.pipe(Effect.provide(BunServices.layer), BunRuntime.runMain); ``` See the [client README](https://github.com/timmo001/ha-bridge/tree/main/packages/client#readme) for watching, action responses, calendars and errors. ## Home Assistant library ```bash bun add @timmo001/effect-ha effect ``` `connect` opens and authenticates a WebSocket session with `callAction`, `getConfig` and raw `request`. The library also has typed action builders (`Light`, `Switch`, `Cover`, `Climate`, `InputBoolean`, `InputNumber`, `AssistSatellite` and `Calendar`), the `EntityState` schema, frontend-style entity naming and `cameraSnapshot`. See the [`effect-ha` README](https://github.com/timmo001/ha-bridge/tree/main/packages/effect-ha#readme) for details. --- # Bar JSON Source: https://ha-bridge.timmo.dev/reference/bar-json `watch entity --bar-json`, `cover watch` and `climate watch` print one JSON object per line: once straight away, then on every change. Any status bar or script that reads JSON lines can use them, including [Waybar](https://github.com/Alexays/Waybar) and [Quickshell](https://quickshell.org). ## Output ```json {"class":"23.3","name":"Living Room Thermostat Temperature","text":"23.3 °C","tooltip":"23.3 °C"} ``` | Field | Value | | --- | --- | | `text` | The label to show. | | `tooltip` | The hover text. | | `class` | A class name for styling. | | `name` | The entity's display name. Left out when the entity has no name. | `text`, `tooltip` and `class` match Waybar's custom module JSON, so Waybar reads the line as it is and ignores `name`. `name` is built the way the Home Assistant frontend names entities: the device name and the entity's own name together, such as `Living Room Thermostat Temperature`. The bridge reads the entity and device registries when it connects, so this costs no extra request per watcher. When the registries don't give a name, the entity's `friendly_name` is used. ## How `watch entity` fills the fields A state is "on" only when it is exactly `on`. Everything else, including `off`, `unavailable` and sensor readings, is "off". | Field | On | Off | | --- | --- | --- | | `text` | `--icon`, or the state with its unit, then `--text-on` | `--icon`, or the state with its unit, then `--text-off` | | `tooltip` | `--tooltip-on`, or the state with its unit | `--tooltip-off`, or the state with its unit | | `class` | `--class-on`, or the raw state | `--class-off`, or the raw state | `--icon` and the appended text are joined with a space. The unit comes from the entity's `unit_of_measurement` attribute. ## Flags | Flag | Effect | | --- | --- | | `--bar-json` | Print bar JSON instead of the raw state. | | `--icon` | Text to show instead of the state. | | `--text-on` | Text appended when the entity is on. | | `--text-off` | Text appended when the entity is off. | | `--tooltip-on` | Tooltip when the entity is on. | | `--tooltip-off` | Tooltip when the entity is off. | | `--class-on` | Class when the entity is on. | | `--class-off` | Class when the entity is off. | ## Example ```bash ha-bridge watch entity input_boolean.guest_mode \ --bar-json \ --text-on "Guest" \ --tooltip-on "Guest mode is on" \ --tooltip-off "Guest mode is off" \ --class-on active \ --class-off inactive ``` With guest mode on: ```json {"class":"active","name":"Guest mode","text":"on Guest","tooltip":"Guest mode is on"} ``` With it off: ```json {"class":"inactive","name":"Guest mode","text":"off","tooltip":"Guest mode is off"} ``` ## Waybar ```jsonc "custom/guest_mode": { "exec": "ha-bridge watch entity input_boolean.guest_mode --bar-json --text-on Guest --class-on active --class-off inactive", "return-type": "json", "restart-interval": 5 } ``` `restart-interval` restarts the watcher if it exits, for example while the bridge restarts. ## Scripts ```bash ha-bridge watch entity input_boolean.guest_mode --bar-json | while IFS= read -r line; do jq -r '.text' <<< "$line" done ``` --- # Commands Source: https://ha-bridge.timmo.dev/reference/commands Every command and its help, as `ha-bridge --help` prints it. Each command also accepts the global flags listed under [`ha-bridge`](#ha-bridge). ## `ha-bridge` ```text DESCRIPTION One shared Home Assistant connection for your machine, served to local apps over a single socket USAGE ha-bridge [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) GLOBAL FLAGS --help, -h Show help information --version, -v Show version information --wizard Start wizard mode for a command --completions Print shell completion script (choices: bash, zsh, fish, sh) --log-level Sets the minimum log level (choices: all, trace, debug, info, warn, warning, error, fatal, none) SUBCOMMANDS serve Hold the shared Home Assistant connection and serve it on the bridge socket setup Set the Home Assistant URL and access token watch, w Watch entities through the bridge assist_satellite, as Assist satellite actions input_boolean, ib Input boolean actions input_number, in Input number actions light, l Light actions switch, s Switch actions cover, c Cover actions climate, cl Climate actions camera Camera actions ``` ## `ha-bridge serve` ```text DESCRIPTION Hold the shared Home Assistant connection and serve it on the bridge socket USAGE ha-bridge serve [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge setup` ```text DESCRIPTION Set the Home Assistant URL and access token USAGE ha-bridge setup [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge watch` Alias: `ha-bridge w` ```text DESCRIPTION Watch entities through the bridge USAGE ha-bridge watch [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge watch entity` Alias: `ha-bridge watch e` ```text DESCRIPTION Print an entity's state now and on every change USAGE ha-bridge watch entity [flags] ARGUMENTS entity_id string Entity to watch, for example light.office FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) --bar-json Print one bar JSON object per state --icon string Text to show instead of the state --text-on string Text appended when the entity is on --text-off string Text appended when the entity is off --tooltip-on string Tooltip when the entity is on --tooltip-off string Tooltip when the entity is off --class-on string Class when the entity is on --class-off string Class when the entity is off ``` ## `ha-bridge assist_satellite` Alias: `ha-bridge as` ```text DESCRIPTION Assist satellite actions USAGE ha-bridge assist_satellite [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge assist_satellite announce` Alias: `ha-bridge assist_satellite a` ```text DESCRIPTION Announce a message on an area's satellites USAGE ha-bridge assist_satellite announce [flags] ARGUMENTS area_id string Area to announce in message string Message to announce FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_boolean` Alias: `ha-bridge ib` ```text DESCRIPTION Input boolean actions USAGE ha-bridge input_boolean [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_boolean turn-on` Alias: `ha-bridge input_boolean on` ```text DESCRIPTION Turn on USAGE ha-bridge input_boolean turn-on [flags] ARGUMENTS name string Entity name without the input_boolean. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_boolean turn-off` Alias: `ha-bridge input_boolean off` ```text DESCRIPTION Turn off USAGE ha-bridge input_boolean turn-off [flags] ARGUMENTS name string Entity name without the input_boolean. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_boolean toggle` Alias: `ha-bridge input_boolean t` ```text DESCRIPTION Toggle USAGE ha-bridge input_boolean toggle [flags] ARGUMENTS name string Entity name without the input_boolean. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_number` Alias: `ha-bridge in` ```text DESCRIPTION Input number actions USAGE ha-bridge input_number [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_number increment` ```text DESCRIPTION Raise the value by one step USAGE ha-bridge input_number increment [flags] ARGUMENTS name string Entity name without the input_number. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_number decrement` ```text DESCRIPTION Lower the value by one step USAGE ha-bridge input_number decrement [flags] ARGUMENTS name string Entity name without the input_number. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge input_number set-value` ```text DESCRIPTION Set the value USAGE ha-bridge input_number set-value [flags] ARGUMENTS name string Entity name without the input_number. prefix value string New value FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge light` Alias: `ha-bridge l` ```text DESCRIPTION Light actions USAGE ha-bridge light [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge light turn-on` Alias: `ha-bridge light on` ```text DESCRIPTION Turn on USAGE ha-bridge light turn-on [flags] ARGUMENTS name string Entity name without the light. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge light turn-off` Alias: `ha-bridge light off` ```text DESCRIPTION Turn off USAGE ha-bridge light turn-off [flags] ARGUMENTS name string Entity name without the light. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge light toggle` Alias: `ha-bridge light t` ```text DESCRIPTION Toggle USAGE ha-bridge light toggle [flags] ARGUMENTS name string Entity name without the light. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge switch` Alias: `ha-bridge s` ```text DESCRIPTION Switch actions USAGE ha-bridge switch [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge switch turn-on` Alias: `ha-bridge switch on` ```text DESCRIPTION Turn on USAGE ha-bridge switch turn-on [flags] ARGUMENTS name string Entity name without the switch. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge switch turn-off` Alias: `ha-bridge switch off` ```text DESCRIPTION Turn off USAGE ha-bridge switch turn-off [flags] ARGUMENTS name string Entity name without the switch. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge switch toggle` Alias: `ha-bridge switch t` ```text DESCRIPTION Toggle USAGE ha-bridge switch toggle [flags] ARGUMENTS name string Entity name without the switch. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge cover` Alias: `ha-bridge c` ```text DESCRIPTION Cover actions USAGE ha-bridge cover [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge cover watch` Alias: `ha-bridge cover w` ```text DESCRIPTION Print bar JSON now and on every change USAGE ha-bridge cover watch [flags] ARGUMENTS name string Entity name without the cover. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge cover position` ```text DESCRIPTION Set the position USAGE ha-bridge cover position [flags] ARGUMENTS name string Entity name without the cover. prefix position string Position from 0 to 100 FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge cover tilt-position` ```text DESCRIPTION Set the tilt position USAGE ha-bridge cover tilt-position [flags] ARGUMENTS name string Entity name without the cover. prefix position string Position from 0 to 100 FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge cover close` ```text DESCRIPTION Close the cover USAGE ha-bridge cover close [flags] ARGUMENTS name string Entity name without the cover. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge climate` Alias: `ha-bridge cl` ```text DESCRIPTION Climate actions USAGE ha-bridge climate [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge climate watch` Alias: `ha-bridge climate w` ```text DESCRIPTION Print bar JSON now and on every change USAGE ha-bridge climate watch [flags] ARGUMENTS name string Entity name without the climate. prefix FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge climate fan-mode` ```text DESCRIPTION Set the fan mode USAGE ha-bridge climate fan-mode [flags] ARGUMENTS name string Entity name without the climate. prefix mode string Fan mode, for example 1 or auto FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge camera` ```text DESCRIPTION Camera actions USAGE ha-bridge camera [flags] FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` ## `ha-bridge camera snapshot` ```text DESCRIPTION Save the camera's current image USAGE ha-bridge camera snapshot [flags] ARGUMENTS name string Entity name without the camera. prefix output string File to write the image to FLAGS --socket string Path to the bridge socket (default: $HA_BRIDGE_SOCK, then $XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock) ``` --- # Bridge protocol Source: https://ha-bridge.timmo.dev/reference/protocol The bridge serves [Effect](https://effect.website) RPCs as newline-delimited JSON on a Unix socket. From TypeScript, use [`@timmo001/effect-ha-bridge`](/libraries/), which handles the framing for you. This page is for other languages and for debugging. The socket is at the [socket path](/configuration/#socket-path), in a directory only you can open (`0700`), and is itself only accessible by you (`0600`). ## RPCs | RPC | Payload | Result | | --- | --- | --- | | `GetEntity` | `{ "entityId": string }` | An entity update, or `null` when the bridge has no state for it | | `WatchEntity` | `{ "entityId": string }` | A stream of entity updates: the current state, if any, then every change | | `CallAction` | `{ "action": string, "data"?: object, "target"?: object, "return_response"?: boolean }` | The action's response when `return_response` is `true`, otherwise `null` | | `GetConfig` | `null` | Home Assistant's config, such as `time_zone`, `location_name` and `version` | | `CameraSnapshot` | `{ "entityId": "camera." }` | `{ "contentType": string, "data": string }`, with the image bytes base64-encoded in `data` | An entity update is `{ "state": EntityState, "name": string }`, where `state` is the state object Home Assistant sends (`entity_id`, `state`, `attributes` and timestamps) and `name` is the display name described in [Bar JSON](/reference/bar-json/#output). `CallAction`, `GetConfig` and `CameraSnapshot` fail with `HomeAssistantError` (`{ "_tag": "HomeAssistantError", "message": string }`) when Home Assistant rejects the request or the bridge isn't connected to it. `CallAction` takes the same `action`, `data` and `target` keys as actions in Home Assistant automations. ## Messages Each line is one JSON message. Send a request: ```json {"_tag":"Request","id":"1","tag":"GetEntity","payload":{"entityId":"sun.sun"},"headers":[]} ``` A single result comes back as an `Exit` with the same ID: ```json {"_tag":"Exit","requestId":"1","exit":{"_tag":"Success","value":{"state":{"entity_id":"sun.sun","state":"above_horizon","attributes":{}},"name":"Sun"}}} ``` A failure has `"_tag":"Failure"` and a `cause` list instead of `value`. An RPC's own error is a `Fail` entry: ```json {"_tag":"Exit","requestId":"2","exit":{"_tag":"Failure","cause":[{"_tag":"Fail","error":{"_tag":"HomeAssistantError","message":"camera snapshot: StatusCode: non 2xx status code (404 GET ...)"}}]}} ``` A malformed request comes back as a `Die` entry, such as `{"_tag":"Die","defect":"Expected null"}` when `GetConfig` has no `payload`. Keep the connection open until the reply arrives. When the client closes its side, the bridge ends that client's requests that are still running. Streams send `Chunk` messages, each with one or more values: ```json {"_tag":"Chunk","requestId":"1","values":[{"state":{"entity_id":"sun.sun","state":"above_horizon","attributes":{}},"name":"Sun"}]} ``` After each chunk, the bridge waits for an `Ack` before sending the next one: ```json {"_tag":"Ack","requestId":"1"} ``` To stop a stream, send `{"_tag":"Interrupt","requestId":"1"}` or close the connection. ## Try it ```bash socket="$XDG_RUNTIME_DIR/ha-bridge/ha-bridge.sock" { echo '{"_tag":"Request","id":"1","tag":"GetConfig","payload":null,"headers":[]}' sleep 1 } | socat - "UNIX-CONNECT:$socket" ``` --- # Running the bridge Source: https://ha-bridge.timmo.dev/running `ha-bridge serve` holds the Home Assistant connection. Every other command needs it running, so it normally runs as a systemd user service. ## User service The packages install `ha-bridge.service` as a user unit. The Arch package enables it for every user's future logins. To start it now, or after installing a `.deb` or `.rpm`: ```bash systemctl --user daemon-reload systemctl --user enable --now ha-bridge.service ``` Check it's connected: ```bash systemctl --user status ha-bridge.service journalctl --user -u ha-bridge.service -f ``` A healthy start logs `Bridge listening`, `Cached entity naming` and `Bridge subscribed to Home Assistant`. The service restarts 5 seconds after it fails, for example when it starts before you've run [setup](/configuration/). User services only run while you're logged in. To keep the bridge running after you log out, enable lingering: ```bash sudo loginctl enable-linger "$USER" ``` ## Connection behaviour When the bridge connects to Home Assistant, it: 1. Authenticates with your token. 2. Subscribes to `state_changed` events, then reads every entity's state into its cache. 3. Reads the entity and device registries, so watchers get the same display names the Home Assistant frontend shows. If the connection drops, the bridge reconnects every 5 seconds. Watchers stay connected to the bridge and get every entity's fresh state once it's back. Actions fail with "the bridge is not connected to Home Assistant" until then. ## Upgrades The Arch package restarts a running service after an upgrade, so it uses the new binary straight away. For other installs, restart it yourself: ```bash systemctl --user restart ha-bridge.service ``` Watchers exit when the bridge restarts. Run them under something that restarts them, such as a status bar's restart interval or a systemd unit. ## Run in the foreground To troubleshoot, stop the service and run the bridge in a terminal with debug logs: ```bash systemctl --user stop ha-bridge.service ha-bridge serve --log-level debug ``` Use `--socket` to run a second bridge beside the service, for example with a different config: ```bash XDG_CONFIG_HOME=/tmp/ha-test ha-bridge serve --socket /tmp/ha-test.sock ha-bridge light toggle desk --socket /tmp/ha-test.sock ``` ## Install the unit by hand When you build from source, copy the unit and point `ExecStart` at your binary: ```bash mkdir -p ~/.config/systemd/user cp .scripts/linux/ha-bridge.service ~/.config/systemd/user/ sed -i "s#/usr/bin/ha-bridge#$(command -v ha-bridge)#" ~/.config/systemd/user/ha-bridge.service systemctl --user daemon-reload systemctl --user enable --now ha-bridge.service ``` --- # Actions Source: https://ha-bridge.timmo.dev/using/actions Action commands send one Home Assistant action through the bridge and exit. They exit with status 1 and print the error when the bridge isn't running or Home Assistant rejects the action. ## Entity names Action commands take the entity name **without** its domain, because the command already says which domain it acts on: ```bash # Acts on light.bedroom_lamp ha-bridge light turn-on bedroom_lamp ``` [Watch commands](/using/watching/) are different: `watch entity` takes the full entity ID. ## Lights, switches and input booleans `light` (`l`), `switch` (`s`) and `input_boolean` (`ib`) each have `turn-on` (`on`), `turn-off` (`off`) and `toggle` (`t`): ```bash ha-bridge light toggle bedroom_lamp ha-bridge switch turn-on desk_fan ha-bridge input_boolean turn-off guest_mode # The same, with aliases for keyboard shortcuts ha-bridge l t bedroom_lamp ha-bridge s on desk_fan ha-bridge ib off guest_mode ``` ## Input numbers `input_number` (`in`) raises or lowers a helper by its step, or sets a value: ```bash ha-bridge input_number increment target_temperature ha-bridge input_number decrement target_temperature ha-bridge input_number set-value target_temperature 23.5 ``` ## Covers `cover` (`c`) sets a position or tilt position from 0 to 100, or closes the cover: ```bash ha-bridge cover position curtain 30 ha-bridge cover tilt-position office_blind 40 ha-bridge cover close curtain ``` ## Climate `climate` (`cl`) sets a fan mode. Use one of the modes the entity lists in its `fan_modes` attribute: ```bash ha-bridge climate fan-mode air_conditioner auto ``` ## Assist satellites `assist_satellite announce` (`as a`) announces a message on every satellite in an area. Pass the **area ID**, then the message in quotes: ```bash ha-bridge assist_satellite announce living_room "Dinner is ready" ``` ## Camera snapshots `camera snapshot` saves a camera's current image: ```bash ha-bridge camera snapshot front_door /tmp/front-door.jpg ``` The image is written to a temporary file first and then renamed, so anything reading the file never sees a partial image. It's readable only by you (`0600`). ## Actions at a glance | Command | Home Assistant action | Target | | --- | --- | --- | | `light turn-on`, `turn-off`, `toggle` | `light.turn_on`, `turn_off`, `toggle` | `light.` | | `switch turn-on`, `turn-off`, `toggle` | `switch.turn_on`, `turn_off`, `toggle` | `switch.` | | `input_boolean turn-on`, `turn-off`, `toggle` | `input_boolean.turn_on`, `turn_off`, `toggle` | `input_boolean.` | | `input_number increment`, `decrement`, `set-value` | `input_number.increment`, `decrement`, `set_value` | `input_number.` | | `cover position`, `tilt-position`, `close` | `cover.set_cover_position`, `set_cover_tilt_position`, `close_cover` | `cover.` | | `climate fan-mode` | `climate.set_fan_mode` | `climate.` | | `assist_satellite announce` | `assist_satellite.announce` | `area_id` | | `camera snapshot` | Camera proxy image | `camera.` | Any other action can be called from your own app with [`CallAction`](/libraries/). --- # Shell completions Source: https://ha-bridge.timmo.dev/using/completions Every package installs completions for bash, zsh and fish, so Tab completes commands, aliases and flags in a new shell. When you install the binary yourself, generate the script for your shell: ```bash # bash ha-bridge --completions bash > ~/.local/share/bash-completion/completions/ha-bridge # zsh: any directory on your $fpath ha-bridge --completions zsh > "${fpath[1]}/_ha-bridge" # fish ha-bridge --completions fish > ~/.config/fish/completions/ha-bridge.fish ``` Completion never contacts the bridge or Home Assistant, and works before you've run setup. It doesn't suggest entity names. --- # Watching entities Source: https://ha-bridge.timmo.dev/using/watching Watch commands print an entity's state straight away, then again on every change. Each watcher is a small client of the bridge, so any number of them share the bridge's one Home Assistant connection. ## Any entity `watch entity` (`w e`) takes the **full** entity ID: ```bash ha-bridge watch entity input_boolean.guest_mode ``` Without `--bar-json` it prints the raw state on each line and warns on stderr that scripts should use JSON. With `--bar-json` it prints one JSON object per line for status bars and scripts: ```bash ha-bridge watch entity input_boolean.guest_mode \ --bar-json \ --text-on "Guest" \ --tooltip-on "Guest mode is on" \ --tooltip-off "Guest mode is off" \ --class-on active \ --class-off inactive ``` See [Bar JSON](/reference/bar-json/) for the output and every flag. ## Covers and climate `cover watch` and `climate watch` take the entity name without its domain, like the [action commands](/using/actions/), and always print bar JSON with a summary of the entity: ```bash ha-bridge cover watch office_blind # {"class":"open","name":"Office Blind","text":"open • 40%","tooltip":"open • 40%"} ha-bridge climate watch air_conditioner # {"class":"cool","name":"Air Conditioner","text":"Cool • Low • 21 °C","tooltip":"Cool • Low • 21 °C"} ``` - Covers show the state, then the current tilt position when the cover has one. - Climate entities show the HVAC mode (`cool` as `Cool`), the fan mode (`1` as `Low`, `2` as `High`) and the target temperature. - Both show `unavailable` on its own when the entity is unavailable. ## When the bridge goes away A watcher prints nothing until the bridge has a state for the entity, then keeps running across Home Assistant reconnects. It exits with status 1 when it can't reach the bridge, or when the bridge stops. Run watchers under something that restarts them, such as Waybar's `restart-interval` or a systemd unit with `Restart=on-failure`.