feat: update README files for ha-gateway and ai-gateway, and enhance discord-bot documentation
This commit is contained in:
parent
88c9a77a58
commit
685b6e2054
280
README.md
280
README.md
@ -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
126
ai-gateway/README.md
Normal 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.
|
||||
@ -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
129
ha-gateway/README.md
Normal 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.
|
||||
Loading…
x
Reference in New Issue
Block a user