Repository navigation
feat: expose valve/subdevice run modes (duration/volume) in Home Assistant - #20
Merged
Merged
Conversation
… Subdevice.cs Remove the continue after setting HasFlowMeter so the flowmeter field falls through to SensorBuilder, preserving the "Flowmeter Available" binary_sensor in Subdevice.Sensors. Add a regression assertion to Map_WFC02_ParsesFlowMeterCapability. Add using Ecowitt.Controller.Model.Api to Subdevice.cs and shorten the StagedRunConfig.Mode property declaration from the fully-qualified name.
… merge tests Three quality issues from review: - Log a warning when UpsertGateway returns false after staged run config merge (mirrors every other call site in Dispatcher.ConsumerHttp.cs) - Warn and skip dispatch when the staged value for the active run mode is <= 0 (spec: selected-mode input of 0 -> warning, no dispatch) - Add DispatcherMergeTest covering partial merge, assembly, duration/volume dispatch, zero-value no-dispatch, unknown subdevice, and no-start paths
…very RunModeRegistry.ApplicableModes is the single source of truth for which models support run modes. The method already returns early when modes is empty, so the outer model check in the caller was a DRY violation that would require updating in two places when a new model is added.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class Home Assistant control surfaces for Ecowitt valve “parameterized runs” (run for a duration or volume), including MQTT discovery entities, staged per-subdevice configuration stored in the controller, and a guarded command translation path to gateway-native (val_type, val) encoding.
Changes:
- Introduces run-mode capability modeling (
RunModeRegistry) and per-subdevice staged run configuration (StagedRunConfig) with a newSubdeviceRunConfigmessage flow for partial updates + “Start”. - Expands MQTT command handling and HA discovery to publish/select/configure run mode + run parameters and to start runs via a button entity.
- Refactors gateway command encoding to normalize time to seconds and volume to deciliters, with test coverage around conversion, registry behavior, guard logic, and merge/dispatch behavior.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Ecowitt.Controller/Service/Orchestrator/Dispatcher.cs | Adds SubdeviceRunConfig consumption to orchestrator dispatcher. |
| src/Ecowitt.Controller/Service/Orchestrator/Dispatcher.ConsumerHttp.cs | Implements staged run-config merge/start flow and shared command guard. |
| src/Ecowitt.Controller/Service/Mqtt/MqttService.Events.cs | Adds /cmd/* topic routing for run-mode control; improves direct-command deserialization options. |
| src/Ecowitt.Controller/Service/Mqtt/MqttService.DiscoveryPublisher.cs | Publishes HA discovery for run-mode select/number/button entities and retained staged values. |
| src/Ecowitt.Controller/Service/Mqtt/MqttService.cs | Emits run-mode discovery per subdevice during HA discovery emission. |
| src/Ecowitt.Controller/Service/Mqtt/MqttService.Consumer.cs | Subscribes to the full subdevice command subtree (+/cmd/#). |
| src/Ecowitt.Controller/Service/Mqtt/MqttPathBuilder.cs | Adds helpers for run-mode MQTT topics and a cmd/# subtree filter. |
| src/Ecowitt.Controller/Service/Http/HttpPublishingService.cs | Adds ToGatewayRun() mapping and uses it for start commands (seconds/deciliters). |
| src/Ecowitt.Controller/Program.cs | Registers SlimMessageBus produce/consume for SubdeviceRunConfig. |
| src/Ecowitt.Controller/Model/Subdevice.cs | Adds HasFlowMeter and StagedRunConfig to the subdevice model. |
| src/Ecowitt.Controller/Model/Message/Data/SubdeviceRunConfig.cs | New message type for staged run config updates and start trigger. |
| src/Ecowitt.Controller/Model/Mapping/ApiDataExtension.cs | Parses flowmeter field into HasFlowMeter (while still building a sensor). |
| src/Ecowitt.Controller/Model/Discovery/DiscoveryBuilder.cs | Adds discovery config builders for number, select, and button. |
| src/Ecowitt.Controller/Model/Discovery/Config.cs | Extends discovery config DTO with number/select/button fields. |
| src/Ecowitt.Controller/Model/Api/RunMode.cs | New run-mode capability model + registry (duration/volume gating). |
| src/EcoWitt.Controller.Tests/SubdeviceMappingTest.cs | Adds tests for flowmeter parsing and staged run-config defaults. |
| src/EcoWitt.Controller.Tests/SubdeviceCommandConversionTest.cs | New tests for gateway run-value conversion mapping. |
| src/EcoWitt.Controller.Tests/RunModeTest.cs | New tests for run-mode registry defaults and capability gating. |
| src/EcoWitt.Controller.Tests/MqttPathBuilderTest.cs | Adds tests for new cmd-subtree and run-mode topic builders. |
| src/EcoWitt.Controller.Tests/DispatcherMergeTest.cs | New tests for staged merge/start assembly and dispatch behavior. |
| src/EcoWitt.Controller.Tests/DispatcherGuardTest.cs | New tests for guard defaulting/clamping and always-on behavior. |
| src/EcoWitt.Controller.Tests/DiscoveryBuilderTest.cs | Adds tests for new discovery config builders. |
| docs/mqtt.md | Updates direct MQTT command docs to match improved enum/string deserialization. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+67
to
+71
| var selId = DiscoveryBuilder.BuildIdentifier(subdevice.Nickname, "runmode"); | ||
| var selCfg = DiscoveryBuilder.BuildSelectConfig(device, _origin, "Run Mode", selId, | ||
| commandTopic: $"{baseTopic}/cmd/mode", | ||
| options: modes.Select(m => m.HaLabel).ToList()); | ||
| await PublishDiscoveryMessage("select", $"select/{nick}", selCfg); |
Comment on lines
+79
to
+83
| var numId = DiscoveryBuilder.BuildIdentifier(subdevice.Nickname, $"run{param}"); | ||
| var numCfg = DiscoveryBuilder.BuildNumberConfig(device, _origin, $"Run {m.HaLabel}", numId, | ||
| commandTopic: $"{baseTopic}/cmd/set/{param}", | ||
| min: m.Min, max: m.Max, step: 1, unitOfMeasurement: unit, entityCategory: "config"); | ||
| await PublishDiscoveryMessage("number", $"number/{nick}_{param}", numCfg); |
| - **`<base>/+/subdevices/+/cmd/set/volume`** — run volume in liters (plain integer; flow-capable valves only). | ||
| - **`<base>/+/subdevices/+/cmd/start`** — start a run using the currently staged mode + value. | ||
|
|
||
| All subdevice command topics (the four above plus `cmd` and `cmd/homeassistant`) are covered by a single `<base>/+/subdevices/+/cmd/#` subscription. A run mode the device doesn't support (e.g. `Volume` on a flow-less valve) is rejected; a `Start` with a missing/invalid value falls back to a safe default (3 min / 5 L). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Exposes the Ecowitt subdevice "run for a duration / volume" capability as first-class Home Assistant entities, so users trigger device-self-closing runs instead of orchestrating open → HA-timer → close. The command path already existed (
SubdeviceApiCommandcarriesDuration/Unit) but was never exposed to HA or tested on hardware; this builds the control surface and verifies the encoding against real devices.Safety motivation: with HA timers, a lost close command leaves a valve stuck open (flooding). With device-side duration, the single start command carries its own deadline — the worst case flips from "stuck open" to "didn't start."
Entities (capability-driven, per controllable subdevice)
On top of the existing always-on switch (unchanged):
select— only when 2+ modes applynumbers —entity_category: configbuttonCapability resolver: Duration for all controllable subdevices (WFC01/WFC02/AC1100); Volume for WFC01 (flow built in) and WFC02 only when
flowmeter == 1; never for AC1100. Single-mode devices skip the select.Architecture
SubdeviceinDeviceStore. A newSubdeviceRunConfigbus message carries partial updates + a Start flag;MqttServicestays a pure translator (+/subdevices/+/cmd/#subscription routes the new topics);Dispatchermerges and, on Start, routes an assembledSubdeviceApiCommandthrough a sharedGuardRunCommandinto the existingSubdeviceCommandDispatchpath.SendSubdeviceCommand.ToGatewayRun): time → secondsval_type:0(×60/×3600); volume → decilitersval_type:3(×10). Verified on hardware (a 5 L command reads backval:50).Unit.HasValue): a bare always-on Start (the HA switch) is left untouched;null/≤0/negative → default; over-max → clamp. Clamping before the multiply makes overflow impossible.Bug fixes folded in
PropertyNameCaseInsensitive+JsonStringEnumConverter) so the documented lowercase payload actually binds;docs/mqtt.mdcorrected.Smoke tested — all green
-1defaults to 3 min.The shared-protocol assumption for AC1100/WFC01 held — all three encode runs identically.
Known limitation (documented, v1)
Staged config may reset to defaults after a controller restart (retained replay arrives before the subdevice is re-polled into the store). Device-side runs are unaffected; the user re-sets the number. Proper fix is a follow-up.
Tests
210/210 (33 new across conversion, registry, capability resolver, flowmeter, discovery builders, guard, path-builder). Build clean.
Out of scope
Cycle mode (registry-extensible), live state reflection, per-subdevice configurable defaults.