feat: update README files for ha-gateway and ai-gateway, and enhance discord-bot documentation
All checks were successful
CI / test (push) Successful in 1m12s
CI / build-ai-gateway (push) Successful in 3m0s
CI / build-ha-gateway (push) Successful in 44s
CI / build-discord-bot (push) Successful in 50s

This commit is contained in:
Nik Afiq 2026-05-06 19:33:06 +09:00
parent 88c9a77a58
commit 685b6e2054
4 changed files with 472 additions and 266 deletions

280
README.md
View File

@ -1,151 +1,90 @@
# home-services # home-services
`home-services` is a Go mono-repo for internal home-control services built `home-services` is a Go workspace for internal home-control services. It keeps
around a gRPC gateway for Home Assistant. Discord and Alexa clients are meant Home Assistant behind a gRPC gateway, adds an AI command gateway backed by
to talk to the gateway instead of integrating with Home Assistant directly. Ollama, and exposes both through a Discord slash-command bot.
## Architecture ## Architecture
The repo uses hexagonal architecture: The services follow hexagonal architecture:
- the core domain is plain Go data and interfaces - `internal/core` contains domain types and ports
- adapters at the edges handle gRPC, HTTP, Discord, telemetry, and logging - `internal/app` contains use-case orchestration
- dependencies point inward only - `internal/adapters` contains gRPC, Discord, Home Assistant, Ollama, logging,
and telemetry edges
- dependencies point inward toward the app and core packages
That means the business logic does not need to know about Discord, protobuf Service flow:
transport details, or Home Assistant HTTP specifics.
### Service relationship
```text ```text
Discord bot ->\ Discord users
\ |
-> ha-gateway -> Home Assistant v
/ discord-bot -----> ha-gateway -----> Home Assistant REST API
Alexa bridge ->/ |
v
ai-gateway ------> Ollama
|
v
ha-gateway
``` ```
- `discord-bot` is an internal gRPC client of `ha-gateway` The protobuf contracts live under `proto/`. Generated Go code is committed
- `alexa-bridge` is planned as another internal gRPC client of `ha-gateway` under `gen/` and shared by all modules through the Go workspace.
- `ha-gateway` is the only service that talks to Home Assistant directly
### Proto-first design
All service contracts are defined in `proto/ha/v1/`. Generated Go code is
committed under `gen/` and used by both clients and servers.
This gives every service the same:
- RPC method names
- request and response message types
- protobuf serialization rules
- gRPC client/server bindings
## Services ## Services
### ha-gateway ### `ha-gateway`
`ha-gateway` is the single internal gRPC gateway to Home Assistant. It should Internal gRPC gateway for Home Assistant.
not be exposed publicly.
- Port: `50051` - Default port: `50051`
- Deployment model: internal-only, typically ClusterIP - Talks to Home Assistant through the REST API
- Implemented: - Implements entity state lookup and discovery
- `EntityService`: `GetState`, `ListStates` - Implements light control and light discovery
- `LightService`: `TurnOn`, `TurnOff`, `Toggle`, `ListLights` - Implements switch discovery
- `SwitchService`: `ListSwitches` - Stubs switch control and event streaming
- Stubbed: - Supports optional mTLS when `TLS_DIR` is set
- `SwitchService`: `TurnOn`, `TurnOff`, `Toggle` - Exposes gRPC health checks and reflection
- `EventService`
- Observability:
- structured logging via `slog`
- `LOG_FORMAT=json` is suitable for production log pipelines
- `LOG_FORMAT=text` is more readable locally
- OpenTelemetry traces and metrics export over OTLP gRPC
Config: See [ha-gateway/README.md](https://gitea.nik4nao.com/nik/home-services/src/branch/main/ha-gateway/README.md).
| Variable | Default | Description | ### `ai-gateway`
| --- | --- | --- |
| `GRPC_PORT` | `50051` | gRPC listen port |
| `HA_BASE_URL` | empty | Base URL for Home Assistant |
| `HA_TOKEN` | none | Home Assistant long-lived access token |
| `OTEL_ENDPOINT` | empty | OTLP gRPC collector endpoint; empty disables telemetry |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` |
| `LOG_FORMAT` | `json` | `json` or `text` |
Auth: Internal gRPC gateway for AI-assisted home commands.
Inbound auth is not implemented yet. The current recommendation is mTLS between - Default port: `50052`
internal services. A per-client API key interceptor is a possible alternative - Calls Ollama for intent extraction and model listing
for simpler environments. - Calls `ha-gateway` for light discovery and light actions
- Caches light discovery results for prompt context
- Supports optional mTLS when `TLS_DIR` is set
- Exposes gRPC health checks; reflection is enabled in debug logs
### discord-bot See [ai-gateway/README.md](https://gitea.nik4nao.com/nik/home-services/src/branch/main/ai-gateway/README.md).
`discord-bot` provides Discord slash commands for home control and discovery. ### `discord-bot`
- Connects to `ha-gateway` over internal gRPC Discord slash-command process for home control.
- Supports slash-command control for lights
- Supports list/discovery responses for lights and switches
- Observability:
- structured logging via `slog`
- OpenTelemetry traces and metrics via OTLP gRPC
Config: - Registers `/light`, `/switch`, and `/ai` commands
- Calls `ha-gateway` for direct Home Assistant commands
- Calls `ai-gateway` for free-form AI queries and model management
- Supports optional mTLS for gateway clients when `TLS_DIR` is set
| Variable | Default | Description | See [discord-bot/README.md](https://gitea.nik4nao.com/nik/home-services/src/branch/main/discord-bot/README.md).
| --- | --- | --- |
| `DISCORD_TOKEN` | none | Discord bot token |
| `GUILD_ID` | empty | Guild-scoped command registration target |
| `HA_GATEWAY_ADDR` | none | gRPC address of `ha-gateway` |
| `OTEL_ENDPOINT` | empty | OTLP gRPC collector endpoint; empty disables telemetry |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` |
| `LOG_FORMAT` | `json` | `json` or `text` |
Auth:
No additional app-layer auth is implemented yet. The bot currently relies on
Discord authentication and the internal network boundary.
### alexa-bridge
`alexa-bridge` is a planned public HTTPS webhook service that will:
- validate Alexa request signatures
- translate Alexa directives into gRPC calls to `ha-gateway`
- keep `ha-gateway` off the public internet
Status:
- stubbed concept only
- not implemented in this repo yet
## Repo Structure ## Repo Structure
```text ```text
home-services/ home-services/
├── proto/ # Source of truth for protobuf service contracts ├── ai-gateway/ # AI gRPC gateway backed by Ollama and ha-gateway
│ └── ha/v1/ ├── discord-bot/ # Discord slash-command bot
├── gen/ # Committed generated Go protobuf/gRPC code; do not edit ├── gen/ # Committed generated protobuf/gRPC Go code
├── ha-gateway/ ├── ha-gateway/ # Home Assistant gRPC gateway
│ ├── cmd/gateway/ # Process entrypoint and startup wiring ├── proto/ # Source protobuf contracts
│ └── internal/
│ ├── core/ # Pure domain and port definitions, no framework logic
│ ├── app/ # Orchestration between ports
│ ├── adapters/ # gRPC handlers, HA client, and other I/O
│ ├── logger/ # Context-aware slog helpers
│ └── telemetry/ # OpenTelemetry setup and shutdown
├── discord-bot/
│ ├── cmd/bot/ # Process entrypoint and Discord session wiring
│ └── internal/
│ ├── app/ # Command orchestration and formatting
│ ├── adapters/ # Discord interaction handlers and gRPC client
│ ├── core/ports/driven/ # App-facing ha-gateway contract
│ ├── logger/ # Context-aware slog helpers
│ └── telemetry/ # OpenTelemetry setup and shutdown
├── buf.yaml # Buf module config
├── buf.gen.yaml # Buf generation config ├── buf.gen.yaml # Buf generation config
└── go.work # Go workspace for local module development ├── buf.yaml # Buf module config
└── go.work # Go workspace
``` ```
## Local Development ## Local Development
@ -153,58 +92,97 @@ home-services/
Prerequisites: Prerequisites:
- Go `1.26+` - Go `1.26+`
- `buf` - `buf`, when changing `.proto` files
- `grpcurl` - `grpcurl`, for manual gRPC checks
- a running Home Assistant instance - Home Assistant with a long-lived access token
- Ollama, if running AI queries
- A Discord application and bot token, if running `discord-bot`
Setup: Set up dependencies:
```bash ```bash
# Clone and set up workspace
git clone https://gitea.nik4nao.com/nik/home-services
cd home-services
go work sync go work sync
# Regenerate proto (only needed if .proto files change)
buf generate
# Configure ha-gateway
cp ha-gateway/.env.example ha-gateway/.env
# Edit ha-gateway/.env — set HA_BASE_URL and HA_TOKEN
# Run ha-gateway
cd ha-gateway && go run ./cmd/gateway
# Run discord-bot (separate terminal)
cd discord-bot && go run ./cmd/bot
``` ```
Smoke testing with `grpcurl`: Regenerate protobuf code after changing files under `proto/`:
```bash
buf generate
```
Run services in separate terminals:
```bash
cp ha-gateway/.env.example ha-gateway/.env
cd ha-gateway
go run ./cmd/gateway
```
```bash
cp ai-gateway/.env.example ai-gateway/.env
cd ai-gateway
go run ./cmd/gateway
```
```bash
cp discord-bot/.env.example discord-bot/.env
cd discord-bot
go run ./cmd/bot
```
Each service loads `.env` from its current working directory, so run commands
from the service directory when using the example env files.
## Test And Build
Run tests from the workspace root:
```bash
go test ./ha-gateway/... ./ai-gateway/... ./discord-bot/...
```
Build service binaries from the workspace root:
```bash
go build ./ha-gateway/... ./ai-gateway/... ./discord-bot/...
```
Build container images from the workspace root:
```bash
docker build -f ha-gateway/Dockerfile -t ha-gateway:dev .
docker build -f ai-gateway/Dockerfile -t ai-gateway:dev .
docker build -f discord-bot/Dockerfile -t discord-bot:dev .
```
## gRPC Smoke Checks
With `ha-gateway` running locally:
```bash ```bash
# List all lights
grpcurl -plaintext -d '{"domain":"light"}' \ grpcurl -plaintext -d '{"domain":"light"}' \
localhost:50051 ha.v1.EntityService/ListStates localhost:50051 ha.v1.EntityService/ListStates
# Turn on a light
grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' \ grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' \
localhost:50051 ha.v1.LightService/TurnOn localhost:50051 ha.v1.LightService/TurnOn
``` ```
## Building With `ai-gateway` running locally:
```bash ```bash
# Build ha-gateway image (run from repo root) grpcurl -plaintext -d '{"text":"turn on the desk lamp","source":"local"}' \
docker build -f ha-gateway/Dockerfile -t ha-gateway:dev . localhost:50052 ai.v1.AIService/Query
# Build discord-bot image grpcurl -plaintext -d '{}' localhost:50052 ai.v1.AIService/ListModels
docker build -f discord-bot/Dockerfile -t discord-bot:dev .
``` ```
## What's Next ## Configuration Notes
- Auth: mTLS between services using an internal cert-manager CA - `LOG_FORMAT=json` is the production default; `LOG_FORMAT=text` is easier
- SwitchService control implementation locally.
- EventService with Home Assistant WebSocket fan-out broker - `OTEL_ENDPOINT` enables OTLP gRPC traces and metrics. Leave it empty for
- `alexa-bridge` implementation local no-op telemetry.
- Gitea CI pipeline for automated build and push - `TLS_DIR` enables mTLS. The directory must contain `tls.crt`, `tls.key`, and
`ca.crt`.
- Inbound app-layer authorization is not implemented. Keep the gateways on a
trusted internal network or use mTLS.

126
ai-gateway/README.md Normal file
View File

@ -0,0 +1,126 @@
# ai-gateway
`ai-gateway` is an internal gRPC service that turns free-form text into home
assistant actions. It asks Ollama to produce a structured intent, resolves that
intent against known Home Assistant lights, and calls `ha-gateway` for any
approved action.
## Runtime Flow
1. The service loads `.env`, configures logging and telemetry, and starts gRPC
on `GRPC_PORT`.
2. `AIService.Query` receives prompt text and an optional model name.
3. The app refreshes or reads the light cache from `ha-gateway`.
4. Ollama receives a prompt containing the user text and known lights.
5. The app parses the model output as JSON intent data.
6. Supported intents are executed through `ha-gateway`; unsupported or invalid
responses return a plain explanation without taking action.
## gRPC API
Defined in [proto/ai/v1/ai.proto](https://gitea.nik4nao.com/nik/home-services/src/branch/main/proto/ai/v1/ai.proto).
- `AIService.Query`: sends text to the AI command flow
- `AIService.ListModels`: returns model names reported by Ollama
Supported action intents currently focus on lights:
- turn on a light
- turn off a light
- list known lights
- no-op / unknown intent
## Configuration
Environment variables:
| Variable | Default | Description |
| --- | --- | --- |
| `GRPC_PORT` | `50052` | gRPC listen port |
| `OLLAMA_URL` | `http://192.168.7.96:11434` | Ollama base URL |
| `OLLAMA_MODEL` | `llama3` | Default model for queries without an explicit model |
| `OLLAMA_TIMEOUT` | `120s` | HTTP timeout for Ollama calls |
| `HA_GATEWAY_ADDR` | `ha-gateway.home-services.svc.cluster.local:50051` | gRPC address for `ha-gateway` |
| `HA_GATEWAY_SERVER_NAME` | `ha-gateway.home-services.svc.cluster.local` | Expected server name for TLS |
| `TLS_DIR` | empty | Enables mTLS for the server and gateway client when set |
| `OTEL_ENDPOINT` | empty | OTLP gRPC collector endpoint; empty disables telemetry |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` |
| `LOG_FORMAT` | `json` | `json` or `text` |
| `LIGHT_CACHE_TTL` | `60s` | Light discovery cache lifetime |
Example env file: [.env.example](https://gitea.nik4nao.com/nik/home-services/src/branch/main/ai-gateway/.env.example)
When `TLS_DIR` is set, the directory must contain `tls.crt`, `tls.key`, and
`ca.crt`.
## Local Run
Start `ha-gateway` first, then run:
```bash
cp .env.example .env
go run ./cmd/gateway
```
Run from `ai-gateway/` so `godotenv` loads `ai-gateway/.env`.
For local plaintext development:
```text
HA_GATEWAY_ADDR=localhost:50051
TLS_DIR=
```
## Smoke Checks
```bash
grpcurl -plaintext -d '{"text":"list the lights","source":"local"}' \
localhost:50052 ai.v1.AIService/Query
grpcurl -plaintext -d '{}' localhost:50052 ai.v1.AIService/ListModels
```
Reflection is only registered when `LOG_LEVEL=debug`; otherwise call by using
the compiled proto descriptors or generated clients.
## Test And Build
```bash
go test ./...
go build ./...
```
Build the container image from the workspace root:
```bash
docker build -f ai-gateway/Dockerfile -t ai-gateway:dev .
```
Optionally pass a build version:
```bash
docker build -f ai-gateway/Dockerfile --build-arg VERSION=$(git rev-parse --short HEAD) -t ai-gateway:dev .
```
## Package Map
```text
cmd/gateway/ # process entrypoint and wiring
internal/adapters/primary/grpc/ # AIService gRPC server
internal/adapters/secondary/ollama/ # Ollama HTTP client
internal/adapters/secondary/hagateway/ # ha-gateway gRPC client
internal/app/ # query orchestration and intent dispatch
internal/config/ # environment loading
internal/core/domain/ # prompt, intent, and cache types
internal/core/ports/driven/ # app-facing LLM and HA interfaces
internal/logger/ # slog setup
internal/telemetry/ # OpenTelemetry setup
```
## Limitations
- Intent parsing depends on the selected model returning valid JSON.
- Light actions are supported; broader Home Assistant domains are not wired yet.
- The light cache is per-process memory and refreshes by TTL.
- Keep this service internal or protect it with mTLS; it does not implement
separate app-layer authorization.

View File

@ -1,159 +1,132 @@
# discord-bot # discord-bot
`discord-bot` is a Discord slash-command service that talks to `discord-bot` is a Discord slash-command service for home control. It calls
`ha-gateway` over plaintext gRPC and exposes common Home Assistant actions in `ha-gateway` for direct Home Assistant actions and `ai-gateway` for free-form
Discord. AI-assisted commands.
## How It Works ## Runtime Flow
Runtime flow: 1. The process loads `.env`, opens a Discord session, and registers slash
commands.
2. A user runs a command such as `/light list`, `/light on`, or `/ai query`.
3. The Discord adapter validates and routes the interaction to the app layer.
4. The app layer calls `ha-gateway` or `ai-gateway` through secondary gRPC
adapters.
5. The bot sends a Discord response. Long-running AI work is tracked so
shutdown can wait briefly for in-flight requests.
1. The bot starts a Discord session and registers slash commands. ## Commands
2. A user runs a slash command such as `/light list` or `/light on`.
3. The Discord adapter routes the interaction to the app layer.
4. The app layer calls `ha-gateway` through the secondary gRPC adapter.
5. `ha-gateway` talks to Home Assistant and returns the result.
6. The bot formats the response back into a Discord message.
The service follows the same hexagonal structure as `ha-gateway`: ### Lights
- `cmd/bot`: process bootstrap
- `internal/adapters/primary/discord`: slash command registration and handlers
- `internal/app`: command orchestration and response formatting
- `internal/adapters/secondary/gateway`: gRPC client for `ha-gateway`
- `internal/core/ports/driven`: app-facing gateway interface
- `internal/config`: env loading
- `internal/telemetry`: OpenTelemetry setup
## Command Behavior
- List commands respond as regular channel messages.
- Action commands use deferred ephemeral replies, then send a follow-up result.
- Light autocomplete is backed by `ListLights` from `ha-gateway`.
- Switch autocomplete support exists in code, but there is currently no switch
action command that uses it.
## Common Commands
### List lights
```text ```text
/light list /light list
```
Shows all discovered lights in a fixed-width block. Each line includes:
- state emoji: `🟢` on, `🔴` off, `⚠️` unavailable or other state
- friendly name
- raw state
- supported color modes
- kelvin range when available
Example output shape:
```text
🟢 Desk Lamp on color_temp,xy 2000-6535K
🔴 Closet off hs,color_temp 2202-4000K
⚠️ Hall Group unavailable color_temp 2000-6535K hue-group
```
### Turn on a light
```text
/light on light:<entity> brightness:80 color_temp:3000 /light on light:<entity> brightness:80 color_temp:3000
```
Options:
- `light`: required, autocomplete enabled
- `brightness`: optional, `1-100`
- `color_temp`: optional, `2000-6535`
The bot responds with a confirmation such as:
```text
Turned on `Desk Lamp` (brightness 80%, 3000K).
```
### Turn off a light
```text
/light off light:<entity> transition:3 /light off light:<entity> transition:3
```
Options:
- `light`: required, autocomplete enabled
- `transition`: optional, `0-30` seconds
### Toggle a light
```text
/light toggle light:<entity> /light toggle light:<entity>
``` ```
Option: - `light` is required for action commands and uses autocomplete.
- `brightness` is optional and accepts `1-100`.
- `color_temp` is optional and accepts `2000-6535` kelvin.
- `transition` is optional and accepts `0-30` seconds.
- `light`: required, autocomplete enabled ### Switches
### List switches
```text ```text
/switch list /switch list
``` ```
Shows discovered switches in a fixed-width block with: Switch control commands are not exposed yet. `ha-gateway` currently only
implements switch discovery.
- state emoji ### AI
- friendly name
- raw state ```text
- device class /ai query text:<prompt>
/ai model list
/ai model get
/ai model set name:<model>
```
AI queries are sent to `ai-gateway`. The active model is stored in process
memory, so it resets when the bot restarts.
## Configuration ## Configuration
Environment variables: Environment variables:
- `DISCORD_TOKEN`: required Discord bot token | Variable | Default | Description |
- `GUILD_ID`: optional guild ID; if set, commands are registered to that guild | --- | --- | --- |
- `HA_GATEWAY_ADDR`: required gRPC address for `ha-gateway` | `DISCORD_TOKEN` | required | Discord bot token |
- `OTEL_ENDPOINT`: optional OTLP gRPC endpoint; empty disables telemetry | `GUILD_ID` | empty | Guild-scoped command registration target; empty registers global commands |
| `HA_GATEWAY_ADDR` | required | gRPC address for `ha-gateway` |
| `AI_GATEWAY_ADDR` | `ai-gateway.home-services.svc.cluster.local:50052` | gRPC address for `ai-gateway` |
| `TLS_DIR` | empty | Enables mTLS for gateway clients when set |
| `OTEL_ENDPOINT` | empty | OTLP gRPC collector endpoint; empty disables telemetry |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` |
| `LOG_FORMAT` | `json` | `json` or `text` |
Example env file: [`.env.example`](/Users/nik-macbookair/repo/home-service/discord-bot/.env.example) Example env file: [.env.example](https://gitea.nik4nao.com/nik/home-services/src/branch/main/discord-bot/.env.example)
When `TLS_DIR` is set, the directory must contain `tls.crt`, `tls.key`, and
`ca.crt`.
## Local Run ## Local Run
Create an env file and fill in real values:
```bash ```bash
cp discord-bot/.env.example discord-bot/.env cp .env.example .env
```
Then run:
```bash
cd discord-bot
go run ./cmd/bot go run ./cmd/bot
``` ```
## Build Run from `discord-bot/` so `godotenv` loads `discord-bot/.env`.
For local plaintext gateway connections, leave `TLS_DIR` empty and use:
```text
HA_GATEWAY_ADDR=localhost:50051
AI_GATEWAY_ADDR=localhost:50052
```
## Test And Build
```bash ```bash
cd discord-bot go test ./...
go build ./... go build ./...
``` ```
## Docker Build the container image from the workspace root:
Build from the repo root:
```bash ```bash
docker build -f discord-bot/Dockerfile -t discord-bot:dev . docker build -f discord-bot/Dockerfile -t discord-bot:dev .
``` ```
## Current Limitations Optionally pass a build version:
- no switch on/off/toggle command is exposed yet ```bash
- the bot depends on `ha-gateway` being reachable and healthy docker build -f discord-bot/Dockerfile --build-arg VERSION=$(git rev-parse --short HEAD) -t discord-bot:dev .
- list formatting is optimized for monospace code blocks, not rich embeds ```
- authentication/authorization is whatever Discord and the internal network
provide; the bot does not add another auth layer itself ## Package Map
```text
cmd/bot/ # process entrypoint and wiring
internal/adapters/primary/discord/ # slash command registration and handlers
internal/adapters/secondary/gateway/ # ha-gateway gRPC client
internal/adapters/secondary/aigateway/ # ai-gateway gRPC client
internal/app/ # command orchestration and formatting
internal/config/ # environment loading
internal/core/ports/driven/ # app-facing gateway interfaces
internal/modelstore/ # in-memory active AI model store
internal/modelvalidator/ # model availability checks
internal/logger/ # slog setup
internal/telemetry/ # OpenTelemetry setup
```
## Limitations
- Switch commands are discovery-only.
- The active AI model is not persisted.
- The bot relies on Discord auth plus internal gateway/network controls; it
does not implement per-user authorization.
- List output is optimized for monospace Discord messages, not rich embeds.

129
ha-gateway/README.md Normal file
View File

@ -0,0 +1,129 @@
# ha-gateway
`ha-gateway` is the internal gRPC boundary for Home Assistant. It exposes a
small protobuf API to other home services and keeps Home Assistant tokens and
REST details out of clients.
## Runtime Flow
1. The service loads `.env`, configures logging and telemetry, and starts gRPC
on `GRPC_PORT`.
2. App services are wired to a Home Assistant REST adapter.
3. Light and switch discovery caches are refreshed during startup when possible.
4. gRPC clients call entity, light, switch, or event services.
5. The adapter maps requests to Home Assistant REST state and service calls.
## gRPC API
Contracts are defined under [proto/ha/v1](https://gitea.nik4nao.com/nik/home-services/src/branch/main/proto/ha/v1).
Implemented:
- `EntityService.GetState`
- `EntityService.ListStates`
- `LightService.TurnOn`
- `LightService.TurnOff`
- `LightService.Toggle`
- `LightService.ListLights`
- `SwitchService.ListSwitches`
Stubbed:
- `SwitchService.TurnOn`
- `SwitchService.TurnOff`
- `SwitchService.Toggle`
- `EventService`
The server also registers gRPC health checks and reflection.
## Configuration
Environment variables:
| Variable | Default | Description |
| --- | --- | --- |
| `GRPC_PORT` | `50051` | gRPC listen port |
| `HA_BASE_URL` | empty | Home Assistant base URL, for example `http://ha.home.arpa:8123` |
| `HA_TOKEN` | required | Home Assistant long-lived access token |
| `TLS_DIR` | empty | Enables mTLS for the gRPC server when set |
| `OTEL_ENDPOINT` | empty | OTLP gRPC collector endpoint; empty disables telemetry |
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` |
| `LOG_FORMAT` | `json` | `json` or `text` |
Example env file: [.env.example](https://gitea.nik4nao.com/nik/home-services/src/branch/main/ha-gateway/.env.example)
When `TLS_DIR` is set, the directory must contain `tls.crt`, `tls.key`, and
`ca.crt`.
## Local Run
```bash
cp .env.example .env
go run ./cmd/gateway
```
Run from `ha-gateway/` so `godotenv` loads `ha-gateway/.env`.
For local plaintext development:
```text
GRPC_PORT=50051
HA_BASE_URL=http://ha.home.arpa:8123
HA_TOKEN=<long-lived-token>
TLS_DIR=
```
## Smoke Checks
```bash
grpcurl -plaintext -d '{"domain":"light"}' \
localhost:50051 ha.v1.EntityService/ListStates
grpcurl -plaintext -d '{}' localhost:50051 ha.v1.LightService/ListLights
grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' \
localhost:50051 ha.v1.LightService/TurnOn
grpcurl -plaintext -d '{}' localhost:50051 grpc.health.v1.Health/Check
```
## Test And Build
```bash
go test ./...
go build ./...
```
Build the container image from the workspace root:
```bash
docker build -f ha-gateway/Dockerfile -t ha-gateway:dev .
```
Optionally pass a build version:
```bash
docker build -f ha-gateway/Dockerfile --build-arg VERSION=$(git rev-parse --short HEAD) -t ha-gateway:dev .
```
## Package Map
```text
cmd/gateway/ # process entrypoint and wiring
internal/adapters/primary/grpc/ # gRPC service implementations
internal/adapters/secondary/ha/ # Home Assistant REST adapter
internal/app/ # entity, light, and switch orchestration
internal/config/ # environment loading
internal/core/domain/ # domain types
internal/core/ports/ # driving and driven interfaces
internal/logger/ # slog setup
internal/telemetry/ # OpenTelemetry setup
```
## Limitations
- Switch control RPCs return not implemented.
- `EventService` is registered but not implemented.
- Home Assistant WebSocket event streaming is still a TODO.
- Keep this service internal or protect it with mTLS; it does not implement
separate app-layer authorization.