# home-service This workspace contains a Home Assistant gRPC gateway and the protobuf contract it serves. The current implementation is centered on `ha-gateway`, a Go service that: - exposes Home Assistant operations over gRPC - translates gRPC requests into Home Assistant REST API calls - emits OpenTelemetry traces and metrics when configured - keeps protobuf definitions and generated Go stubs in-repo `EntityService` is implemented, `LightService` supports both discovery and control, `SwitchService` supports discovery, and `EventService` is still scaffolded. ## Workspace Layout ```text . ├── proto/ # Source protobuf definitions ├── gen/ # Generated Go protobuf/grpc code (committed) ├── ha-gateway/ # Go gRPC server ├── buf.yaml # Buf module config ├── buf.gen.yaml # Buf codegen config └── go.work # Go workspace linking gen + ha-gateway ``` ## Architecture `ha-gateway` follows a ports-and-adapters structure: - `internal/core/domain`: pure domain types - `internal/core/ports/driving`: interfaces exposed to primary adapters - `internal/core/ports/driven`: interfaces the app layer depends on - `internal/app`: application logic for entity, light, and switch operations - `internal/adapters/primary/grpc`: gRPC handlers and proto/domain mapping - `internal/adapters/secondary/ha`: Home Assistant REST client - `internal/telemetry`: OpenTelemetry setup The runtime flow is: 1. A gRPC client calls `EntityService`, `LightService`, or `SwitchService`. 2. The gRPC adapter maps protobuf messages into domain parameters. 3. The app layer orchestrates the use case. 4. The HA adapter calls Home Assistant's REST API. 5. The response is mapped back into the protobuf response. For discovery-style RPCs, the light and switch apps cache filtered entity lists from Home Assistant state snapshots. The cache is primed at startup on a best-effort basis and lazily refreshed on the first request if startup discovery fails. ## Services ### Implemented - `ha.v1.EntityService` - `GetState` - `ListStates` - `ha.v1.LightService` - `ListLights` - `TurnOn` - `TurnOff` - `Toggle` - `ha.v1.SwitchService` - `ListSwitches` ### Partially Stubbed - `ha.v1.SwitchService` - `TurnOn` - `TurnOff` - `Toggle` ### Stubbed - `ha.v1.EventService` The event path is planned around Home Assistant WebSocket subscriptions, but the WebSocket adapter and fan-out broker are not implemented yet. ## Configuration `ha-gateway` reads configuration from environment variables. A sample file lives at [ha-gateway/.env.example](/Users/nik-macbookair/repo/home-service/ha-gateway/.env.example). | Variable | Required | Default | Notes | | --- | --- | --- | --- | | `HA_TOKEN` | yes | none | Home Assistant long-lived access token | | `GRPC_PORT` | no | `50051` | gRPC listen port | | `HA_BASE_URL` | effectively yes | empty | Example: `http://ha.home.arpa:8123` | | `OTEL_ENDPOINT` | no | empty | OTLP gRPC endpoint; empty disables telemetry | Notes: - startup fails if `HA_TOKEN` is missing - `HA_BASE_URL` is not validated on load, but the gateway cannot reach Home Assistant without it - if `OTEL_ENDPOINT` is empty, the service installs no-op telemetry providers ## Prerequisites - Go `1.26` - `buf` for protobuf generation - a reachable Home Assistant instance - a valid Home Assistant long-lived access token ## Generate Protobuf Code Run from the repo root: ```bash buf generate ``` Generated files are written to `gen/ha/v1`. ## Build Sync the workspace, then build the gateway: ```bash go work sync cd ha-gateway go build ./... ``` ## Run Locally Create a local env file: ```bash cp ha-gateway/.env.example ha-gateway/.env ``` Fill in `HA_TOKEN` and `HA_BASE_URL`, then start the server: ```bash cd ha-gateway go run ./cmd/gateway ``` The gateway listens on `:50051` by default. ## Smoke Test With grpcurl The server registers gRPC reflection, so `grpcurl` can inspect it directly: ```bash grpcurl -plaintext localhost:50051 list grpcurl -plaintext localhost:50051 describe ha.v1.LightService ``` Examples against a locally running gateway: ```bash # List states filtered by domain grpcurl -plaintext -d '{"domain":"light"}' \ localhost:50051 ha.v1.EntityService/ListStates # List discovered lights grpcurl -plaintext -d '{}' \ localhost:50051 ha.v1.LightService/ListLights # List discovered switches grpcurl -plaintext -d '{}' \ localhost:50051 ha.v1.SwitchService/ListSwitches # Get one entity grpcurl -plaintext -d '{"entity_id":"light.living_room"}' \ localhost:50051 ha.v1.EntityService/GetState # Turn on a light at 80% brightness grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' \ localhost:50051 ha.v1.LightService/TurnOn # Toggle a light grpcurl -plaintext -d '{"entity_id":"light.living_room"}' \ localhost:50051 ha.v1.LightService/Toggle ``` ## Docker Build from the repo root: ```bash docker build -f ha-gateway/Dockerfile -t ha-gateway:dev . ``` Run it with the same env file: ```bash docker run --env-file ha-gateway/.env -p 50051:50051 ha-gateway:dev ``` ## Telemetry When `OTEL_ENDPOINT` is set, the gateway exports: - traces via OTLP/gRPC - metrics via OTLP/gRPC The service name is `ha-gateway`. When `OTEL_ENDPOINT` is unset, telemetry is disabled for local development. ## Current Limitations - no authentication/authorization on inbound gRPC requests yet - `SwitchService` control RPCs (`TurnOn`, `TurnOff`, `Toggle`) still return `Unimplemented` - `EventService` is not implemented - Home Assistant event streaming over WebSocket is not implemented - there are currently no unit tests in the repo The auth note in [ha-gateway/cmd/gateway/main.go](/Users/nik-macbookair/repo/home-service/ha-gateway/cmd/gateway/main.go) explicitly calls out API-key and mTLS as future options before exposing the gateway outside a trusted network.