Nik Afiq 8f7024edfa
All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 24s
CI / build-ai-gateway (push) Successful in 1m6s
CI / build-ha-gateway (push) Successful in 1m3s
CI / build-discord-bot (push) Successful in 1m4s
CI / build-alexa-bridge (push) Successful in 1m15s
CI / build-tts-gateway (push) Successful in 1m5s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat(climate): add SetTemperature method to ClimateService
- Implemented SetTemperature in ClimateService for setting an absolute target temperature.
- Updated ClimateServiceClient and ClimateServiceServer interfaces to include SetTemperature.
- Added corresponding handler and tests for SetTemperature in ClimateGRPC.
- Modified ClimateApp to handle SetTemperature requests without clamping.
- Updated climate.proto to define SetTemperatureRequest message.
- Adjusted Dockerfiles to include alexa-bridge dependencies.
2026-07-25 12:08:24 +09:00

177 lines
6.2 KiB
Markdown

# discord-bot
`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.
## 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.
## Commands
### Lights
```text
/light list
/light on light:<entity> brightness:80 color_temp:3000
/light off light:<entity> transition:3
/light toggle light:<entity>
```
- `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.
### Switches
```text
/switch list
/switch on switch:<entity>
/switch off switch:<entity>
/switch toggle switch:<entity>
```
- `switch` is required for action commands and uses autocomplete.
### Air Conditioner
```text
/ac on ac:<entity>
/ac off ac:<entity>
/ac mode ac:<entity> mode:<Cool|Heat|Dry|Auto|Fan>
/ac temp up ac:<entity>
/ac temp down ac:<entity>
```
- `ac` is required for every subcommand and uses autocomplete.
- `mode` is a fixed choice list mapping to Home Assistant HVAC modes
(Cool→`cool`, Heat→`heat`, Dry→`dry`, Auto→`heat_cool`, Fan→`fan_only`).
- `temp up`/`temp down` step the target temperature by one
`target_temp_step` increment, clamped to the entity's `min_temp`/`max_temp`.
### 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.
### Speak
```text
/speak speaker:<name> text:<text>
```
Synthesizes `text` via `tts-gateway` (92-speaker autocomplete) and plays the
result in the invoking user's current voice channel. `tts-gateway` returns
AAC; `discord-bot` transcodes it to Opus locally (`ffmpeg` +
`github.com/jonas747/dca`) before streaming, since Discord's voice transport
requires Opus. **Currently non-functional in practice** - see "Limitations"
below; the synthesis half works, voice playback doesn't yet.
## Configuration
Environment variables:
| 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` |
| `TTS_GATEWAY_ADDR` | `tts-gateway.home-services.svc.cluster.local:50053` | gRPC address for `tts-gateway` |
| `TLS_DIR` | empty | Enables mTLS for `ha-gateway`/`ai-gateway` clients when set - **not currently used for the `tts-gateway` client**, since `tts-gateway`'s own mTLS is still disabled (see its README); that's hardcoded to plaintext until both sides are ready together |
| `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/discord-bot/.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/bot
```
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
go test ./...
go build ./...
```
Build the container image from the workspace root:
```bash
docker build -f discord-bot/Dockerfile -t discord-bot:dev .
```
Optionally pass a build version:
```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, handlers, voice playback (voice.go)
internal/adapters/secondary/gateway/ # ha-gateway gRPC client
internal/adapters/secondary/aigateway/ # ai-gateway gRPC client
internal/adapters/secondary/ttsgateway/ # tts-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
- 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.
- **`/speak` cannot currently join a voice channel at all.** Discord requires
its DAVE end-to-end-encryption protocol for every voice connection as of
March 1, 2026 (non-DAVE clients get disconnected with close code `4017`,
"E2EE/DAVE protocol required"). `github.com/bwmarrin/discordgo` v0.29.0 -
the latest tagged release, and what this module depends on - has no DAVE
support (confirmed by inspecting its source; an upstream PR adding it was
still open, unmerged, as of March 2026). This blocks any discordgo-based
voice bot right now, not just this one. The synthesis half of `/speak`
(calling `tts-gateway`, getting audio back) works correctly; only the
final "join the channel and stream" step fails. Revisit once discordgo
ships DAVE support - deliberately not worked around with an unofficial
fork (e.g. `cartridge-gg/discordgo`'s cgo/libdave binding) for now.