remove rheemcloud pkg, implement against rest api only
Build and Publish / helm-release (push) Has been skipped
Build and Publish / go-binaries (push) Has been skipped
Build and Publish / container-images (push) Has been skipped
Build and Publish / check-chart (push) Successful in 12s

This commit is contained in:
2026-07-05 16:59:53 -04:00
parent dcdf5e4230
commit 047f7c233d
15 changed files with 822 additions and 233 deletions
+24 -13
View File
@@ -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)