generated from rmcguire/go-server-with-otel
remove rheemcloud pkg, implement against rest api only
This commit is contained in:
@@ -9,14 +9,25 @@ Exporter for Rheem EcoNet / Rheemcloud water heaters, built on the
|
||||
`appServices` slice (registers `&econet.EconetService{}`) and the
|
||||
`serviceConfig.LoadEnv()` call (see Config gotcha below).
|
||||
- `pkg/econet/econet.go` — `EconetService` implements `service.AppService`.
|
||||
It **owns the single `*rheemcloud.Client`** and shares it with the gRPC,
|
||||
MCP, and metrics sub-servers. `Init` connects (fail-fast if creds missing);
|
||||
the returned `ShutdownFunc` stops metrics and closes the client.
|
||||
It **owns the single `econetclient.Client`** and shares it with the gRPC,
|
||||
MCP, and metrics sub-servers. `Init` fails fast if creds are missing, then
|
||||
refreshes device state on a ticker **in a background goroutine** (`pollLoop`,
|
||||
interval `pollInterval`) so a slow or unreachable cloud never blocks
|
||||
HTTP/gRPC server startup. The client is stateless REST, so `ShutdownFunc` is
|
||||
effectively a no-op (the poll goroutine stops on ctx cancel).
|
||||
- `pkg/econet/econetclient/` — a minimal EcoNet REST client (ClearBlade
|
||||
backend). `client.go` does the 3 REST calls (auth → `getUserDataForApp` →
|
||||
`dynamicAction` usage) and caches a snapshot under an `RWMutex`; `device.go`
|
||||
decodes the `@...` datapoints into a flat `Device` struct (ported from
|
||||
kevinburke/rheemcloud-go, minus MQTT). `Devices()` is empty until the first
|
||||
`Refresh` succeeds — consumers just see an empty list (no nil-guarding).
|
||||
**No MQTT**, so `WiFiSignal` and `Running`/`RunningState` stay zero.
|
||||
- `pkg/econet/econetgrpc/` — `EconetService` gRPC impl (`ListDevices`,
|
||||
`GetDevice`) + `deviceToProto`. Does **not** own a client; it's injected.
|
||||
- `pkg/econet/econetmcp/` — MCP server at `/api/mcp`; the `list_water_heaters`
|
||||
tool delegates to the gRPC server.
|
||||
- `pkg/econet/econetmetrics/` — OTEL observable gauges + a usage poller.
|
||||
- `pkg/econet/econetmetrics/` — OTEL observable gauges (read the client
|
||||
snapshot at scrape time; no poller of its own).
|
||||
|
||||
## Config gotcha (important)
|
||||
|
||||
@@ -40,16 +51,16 @@ that's all it takes — no extra wiring.
|
||||
|
||||
## Metrics conventions
|
||||
|
||||
- Live state is read at scrape time inside a `RegisterCallback` over
|
||||
`client.Devices()` (cheap, in-memory, always current — the rheemcloud client
|
||||
keeps state fresh over MQTT). Usage (`EnergyUsage`/`WaterUsage`) is REST and
|
||||
slow, so a background poller (`usageInterval`) caches it; the usage callback
|
||||
reads the cache.
|
||||
- Live state and usage are both read at scrape time inside `RegisterCallback`s
|
||||
over `client.Devices()` (cheap, in-memory). The `pollLoop` in `econet.go`
|
||||
keeps the snapshot fresh over REST (`client.Refresh`), which folds daily
|
||||
energy/water usage totals into each `Device`. No usage cache or event
|
||||
draining — the callbacks just read the current snapshot.
|
||||
- **Attributes**: dotted/namespaced `econet.*` (semantic-convention style),
|
||||
low-cardinality only (serial, device_id, friendly_name, type, generic_type,
|
||||
mode, energy_type). **Never** put changing values (timestamps) in attributes
|
||||
— `econet_last_updated_seconds` is a metric *value*, and it's suppressed
|
||||
until the first MQTT update so it never emits a garbage epoch.
|
||||
until the first refresh so it never emits a garbage epoch.
|
||||
- **Units live in metric names** (`_fahrenheit`, `_kwh`, `_gallons`,
|
||||
`_dollars`), NOT via `metric.WithUnit` — the OTEL→Prometheus exporter would
|
||||
otherwise append a second unit suffix (`..._fahrenheit_F`).
|
||||
@@ -59,9 +70,9 @@ that's all it takes — no extra wiring.
|
||||
- Regenerate proto: `make proto` (buf; `PROTO_DIRS=proto/econet/v1alpha1/*`).
|
||||
- Regenerate config schema after config changes: `make schema`.
|
||||
- Build/test: `go build ./... && go test ./...`.
|
||||
- The rheemcloud `*Device` has no exported constructor, so `deviceToProto`
|
||||
isn't unit-testable in isolation; test the pure helpers (`summarizeDevices`,
|
||||
`b2i`) and exercise the rest with a live run.
|
||||
- `econetclient.Device` is a plain struct, so `deviceToProto` and the datapoint
|
||||
decoders (`mode`, `enabled`, `setpoint`, `hotWater`) are unit-testable by
|
||||
hand-building a `Device` or feeding raw JSON to `newDevice`.
|
||||
|
||||
## Live verification (needs a real Rheem account)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user