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` is a Go mono-repo for internal home-control services built
around a gRPC gateway for Home Assistant. Discord and Alexa clients are meant
to talk to the gateway instead of integrating with Home Assistant directly.
`home-services` is a Go workspace for internal home-control services. It keeps
Home Assistant behind a gRPC gateway, adds an AI command gateway backed by
Ollama, and exposes both through a Discord slash-command bot.
## Architecture
The repo uses hexagonal architecture:
The services follow hexagonal architecture:
- the core domain is plain Go data and interfaces
- adapters at the edges handle gRPC, HTTP, Discord, telemetry, and logging
- dependencies point inward only
- `internal/core` contains domain types and ports
- `internal/app` contains use-case orchestration
- `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
transport details, or Home Assistant HTTP specifics.
### Service relationship
Service flow:
```text
Discord bot ->\
\
-> ha-gateway -> Home Assistant
/
Alexa bridge ->/
Discord users
|
v
discord-bot -----> ha-gateway -----> Home Assistant REST API
|
v
ai-gateway ------> Ollama
|
v
ha-gateway
```
- `discord-bot` is an internal gRPC client of `ha-gateway`
- `alexa-bridge` is planned as another internal gRPC client of `ha-gateway`
- `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
The protobuf contracts live under `proto/`. Generated Go code is committed
under `gen/` and shared by all modules through the Go workspace.
## Services
### ha-gateway
### `ha-gateway`
`ha-gateway` is the single internal gRPC gateway to Home Assistant. It should
not be exposed publicly.
Internal gRPC gateway for Home Assistant.
- Port: `50051`
- Deployment model: internal-only, typically ClusterIP
- Implemented:
- `EntityService`: `GetState`, `ListStates`
- `LightService`: `TurnOn`, `TurnOff`, `Toggle`, `ListLights`
- `SwitchService`: `ListSwitches`
- Stubbed:
- `SwitchService`: `TurnOn`, `TurnOff`, `Toggle`
- `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
- Default port: `50051`
- Talks to Home Assistant through the REST API
- Implements entity state lookup and discovery
- Implements light control and light discovery
- Implements switch discovery
- Stubs switch control and event streaming
- Supports optional mTLS when `TLS_DIR` is set
- Exposes gRPC health checks and reflection
Config:
See [ha-gateway/README.md](https://gitea.nik4nao.com/nik/home-services/src/branch/main/ha-gateway/README.md).
| Variable | Default | Description |
| --- | --- | --- |
| `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` |
### `ai-gateway`
Auth:
Internal gRPC gateway for AI-assisted home commands.
Inbound auth is not implemented yet. The current recommendation is mTLS between
internal services. A per-client API key interceptor is a possible alternative
for simpler environments.
- Default port: `50052`
- Calls Ollama for intent extraction and model listing
- 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
- 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
Discord slash-command process for home control.
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 |
| --- | --- | --- |
| `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
See [discord-bot/README.md](https://gitea.nik4nao.com/nik/home-services/src/branch/main/discord-bot/README.md).
## Repo Structure
```text
home-services/
├── proto/ # Source of truth for protobuf service contracts
│ └── ha/v1/
├── gen/ # Committed generated Go protobuf/gRPC code; do not edit
├── ha-gateway/
│ ├── cmd/gateway/ # Process entrypoint and startup wiring
│ └── 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
├── ai-gateway/ # AI gRPC gateway backed by Ollama and ha-gateway
├── discord-bot/ # Discord slash-command bot
├── gen/ # Committed generated protobuf/gRPC Go code
├── ha-gateway/ # Home Assistant gRPC gateway
├── proto/ # Source protobuf contracts
├── 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
@ -153,58 +92,97 @@ home-services/
Prerequisites:
- Go `1.26+`
- `buf`
- `grpcurl`
- a running Home Assistant instance
- `buf`, when changing `.proto` files
- `grpcurl`, for manual gRPC checks
- 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
# Clone and set up workspace
git clone https://gitea.nik4nao.com/nik/home-services
cd home-services
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
# List all lights
grpcurl -plaintext -d '{"domain":"light"}' \
localhost:50051 ha.v1.EntityService/ListStates
# Turn on a light
grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' \
localhost:50051 ha.v1.LightService/TurnOn
```
## Building
With `ai-gateway` running locally:
```bash
# Build ha-gateway image (run from repo root)
docker build -f ha-gateway/Dockerfile -t ha-gateway:dev .
grpcurl -plaintext -d '{"text":"turn on the desk lamp","source":"local"}' \
localhost:50052 ai.v1.AIService/Query
# Build discord-bot image
docker build -f discord-bot/Dockerfile -t discord-bot:dev .
grpcurl -plaintext -d '{}' localhost:50052 ai.v1.AIService/ListModels
```
## What's Next
## Configuration Notes
- Auth: mTLS between services using an internal cert-manager CA
- SwitchService control implementation
- EventService with Home Assistant WebSocket fan-out broker
- `alexa-bridge` implementation
- Gitea CI pipeline for automated build and push
- `LOG_FORMAT=json` is the production default; `LOG_FORMAT=text` is easier
locally.
- `OTEL_ENDPOINT` enables OTLP gRPC traces and metrics. Leave it empty for
local no-op telemetry.
- `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` is a Discord slash-command service that talks to
`ha-gateway` over plaintext gRPC and exposes common Home Assistant actions in
Discord.
`discord-bot` is a Discord slash-command service for home control. It calls
`ha-gateway` for direct Home Assistant actions and `ai-gateway` for free-form
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.
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.
## Commands
The service follows the same hexagonal structure as `ha-gateway`:
- `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
### Lights
```text
/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
```
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
```
Options:
- `light`: required, autocomplete enabled
- `transition`: optional, `0-30` seconds
### Toggle a light
```text
/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
### List switches
### Switches
```text
/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
- friendly name
- raw state
- device class
### AI
```text
/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
Environment variables:
- `DISCORD_TOKEN`: required Discord bot token
- `GUILD_ID`: optional guild ID; if set, commands are registered to that guild
- `HA_GATEWAY_ADDR`: required gRPC address for `ha-gateway`
- `OTEL_ENDPOINT`: optional OTLP gRPC endpoint; empty disables telemetry
| Variable | Default | Description |
| --- | --- | --- |
| `DISCORD_TOKEN` | required | Discord bot token |
| `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
Create an env file and fill in real values:
```bash
cp discord-bot/.env.example discord-bot/.env
```
Then run:
```bash
cd discord-bot
cp .env.example .env
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
cd discord-bot
go test ./...
go build ./...
```
## Docker
Build from the repo root:
Build the container image from the workspace root:
```bash
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
- the bot depends on `ha-gateway` being reachable and healthy
- 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
```bash
docker build -f discord-bot/Dockerfile --build-arg VERSION=$(git rev-parse --short HEAD) -t discord-bot:dev .
```
## 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.