- Added ClimateApp to handle climate entity operations including turning on/off, adjusting temperature, and setting HVAC modes. - Implemented caching mechanism for climate entities to optimize state retrieval. - Created domain model for Climate with attributes such as current temperature, target temperature, and HVAC modes. - Developed unit tests for ClimateApp to ensure functionality and correctness. feat: add RemoteApp for SwitchBot commands - Introduced RemoteApp to facilitate sending commands to SwitchBot devices. - Implemented SendCommand method for executing device commands without state management. chore: update configuration for SwitchBot integration - Added SwitchBotToken and SwitchBotSecret to configuration for enabling SwitchBot Cloud commands. feat: define gRPC services for Climate and Remote operations - Created climate.proto and remote.proto files to define gRPC services for climate management and remote command execution. - Implemented corresponding request and response message structures for gRPC interactions.
6.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repo Overview
home-services is a Go workspace of three internal services for home control, connected by gRPC and sharing committed protobuf-generated code:
Discord users
|
v
discord-bot -----> ha-gateway -----> Home Assistant REST API
|
v
ai-gateway ------> Ollama
|
v
ha-gateway
- ha-gateway (port
50051) — gRPC boundary for Home Assistant. Talks to HA's REST API; implements entity state and light control/discovery and switch discovery; switch control and event streaming are stubbed. - ai-gateway (port
50052) — gRPC service that turns free-form text into home actions. Calls Ollama for intent extraction, resolves intents against a cached light list fromha-gateway, and callsha-gatewayto execute approved actions. - discord-bot — registers
/light,/switch,/aislash commands and callsha-gateway/ai-gatewayvia gRPC clients.
Each service is a separate Go module (own go.mod) joined by go.work at the root, plus a gen module for shared generated code. Module paths are gitea.nik4nao.com/nik/home-services/{ha-gateway,ai-gateway,discord-bot,gen}.
Architecture (hexagonal, per service)
Every service follows the same internal layout, with dependencies pointing inward:
cmd/<entrypoint>/ # process entrypoint and wiring (loads .env, builds adapters, starts gRPC)
internal/adapters/primary/ # inbound edges: gRPC servers, Discord handlers
internal/adapters/secondary/ # outbound edges: HA REST client, Ollama client, ha-gateway/ai-gateway gRPC clients
internal/app/ # use-case orchestration
internal/core/domain/ # domain types
internal/core/ports/ # driving (inbound) and driven (outbound) interfaces
internal/config/ # environment loading
internal/logger/ # slog setup
internal/telemetry/ # OpenTelemetry setup
internal/core has no dependency on adapters — ports are interfaces that adapters implement (driven) or call into (driving). When adding a capability, the usual path is: define/extend a port in core/ports, implement orchestration in app, then wire an adapter in adapters/primary or adapters/secondary.
Protobuf contracts live in proto/ (buf module, ai/v1 and ha/v1 packages). Generated Go code is committed under gen/ and consumed by all three services through the Go workspace — do not hand-edit files in gen/.
tmp/ is reference-only, never a dependency
tmp/ is gitignored — nothing under it is pushed to git, and it should be treated as temporary scratch space for reference material (e.g. tmp/reference/switchbot-control-reference/, a standalone CLI copied in for local discovery/testing against an external API). Rules:
- It's fine to read code under
tmp/for patterns, to run its scripts/tools locally (a discovery script, a CLI, etc.), or to use it as a manual testing aid. - Never make any of the three services (
ha-gateway,ai-gateway,discord-bot) import,go.work use, or otherwise depend on anything undertmp/at build or runtime. Sincetmp/isn't committed, that dependency would silently break for every other clone of the repo (including CI). - If a reference tool under
tmp/lives in its own Go module nested inside this repo'sgo.workworkspace, invoke it withGOWORK=offrather than adding it to the rootgo.work— e.g.GOWORK=off ./scripts/some-tool ...from that tool's own directory. - If something under
tmp/turns out to be genuinely needed at runtime, port the actual logic into the relevant service'sinternal/tree (following the hexagonal layout above) instead of reaching intotmp/from committed code.
Common Commands
Regenerate protobuf code after changing anything under proto/ (requires buf):
buf generate
Run tests / vet (from repo root, or cd into a service and drop the prefix):
go test ./ha-gateway/... ./ai-gateway/... ./discord-bot/...
go vet ./ha-gateway/... ./ai-gateway/... ./discord-bot/...
Run a single test:
cd ha-gateway && go test ./internal/app/... -run TestEntityAppGetState
Build binaries:
go build ./ha-gateway/... ./ai-gateway/... ./discord-bot/...
Run a service locally (each loads .env from its own working directory via godotenv, so cd into the service dir first):
cd ha-gateway && cp .env.example .env && go run ./cmd/gateway
cd ai-gateway && cp .env.example .env && go run ./cmd/gateway
cd discord-bot && cp .env.example .env && go run ./cmd/bot
For local plaintext dev, point gateways at each other with TLS_DIR empty, e.g. HA_GATEWAY_ADDR=localhost:50051, AI_GATEWAY_ADDR=localhost:50052.
Build container images (from repo root, since Dockerfiles reference the whole workspace):
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 the relevant service running):
grpcurl -plaintext -d '{"domain":"light"}' localhost:50051 ha.v1.EntityService/ListStates
grpcurl -plaintext -d '{"entity_id":"light.living_room","brightness_pct":80}' localhost:50051 ha.v1.LightService/TurnOn
grpcurl -plaintext -d '{"text":"turn on the desk lamp","source":"local"}' localhost:50052 ai.v1.AIService/Query
grpcurl -plaintext -d '{}' localhost:50052 ai.v1.AIService/ListModels
Note: ha-gateway always registers gRPC reflection; ai-gateway only registers reflection when LOG_LEVEL=debug.
Testing Conventions
Tests use the standard library testing package only (no testify). Mocks are hand-written structs implementing the relevant core/ports/driven interface with function fields (see ha-gateway/internal/app/entity_test.go for the pattern).
Configuration Notes
- Every service reads
TLS_DIRto enable optional mTLS; when set, the directory must containtls.crt,tls.key, andca.crt. OTEL_ENDPOINTenables OTLP gRPC traces/metrics; leave empty for local no-op telemetry.LOG_FORMAT=jsonis the production default;textis easier to read locally.- None of the services implement app-layer authorization — they rely on being kept on a trusted internal network or on mTLS. Keep this in mind before adding any endpoint that wasn't previously reachable.
CI
.gitea/workflows/ci.yaml runs go vet and go test for all four modules (gen, ai-gateway, ha-gateway, discord-bot), then builds and pushes Docker images for the three services on pushes to main.