Skip to content

Add Configuration of DHW System Function (CDSF) use case#247

Open
andig wants to merge 9 commits into
enbility:devfrom
evcc-io:feat/ca-cdsf
Open

Add Configuration of DHW System Function (CDSF) use case#247
andig wants to merge 9 commits into
enbility:devfrom
evcc-io:feat/ca-cdsf

Conversation

@andig

@andig andig commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Adds the Configuration of DHW System Function (CDSF) use case (EEBus UC TS V1.0.0) as a Configuration Appliance client actor in usecases/ca/cdsf.

Scenarios (the DHW circuit has to support at least one of 1 and 2):

  1. Set DHW operation mode — OperationModes, CurrentOperationMode, WriteOperationMode (write of currentOperationModeId via hvacSystemFunctionListData, gated by isOperationModeIdChangeable)
  2. Start one-time DHW loading — StartOneTimeDhw (writes overrunStatus=active on the oneTimeDhw overrun resolved via hvacOverrunDescriptionListData)
  3. Stop one-time DHW loading — StopOneTimeDhw (writes overrunStatus=inactive), gated by isOverrunStatusChangeable

Server is a DHWCircuit entity; the client subscribes to the HVAC server feature (no binding, per spec).

Stacked on #239 (Hvac and Setpoint client features) — only the last commit is specific to this PR.

🤖 Generated with Claude Code

andig and others added 4 commits July 17, 2026 12:22
Groundwork for the HVAC system function and temperature use cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@volschin

Copy link
Copy Markdown

Thanks for putting this together — the overall CDSF structure and public API look very useful. I compared it with our VR940-backed DHW implementation, and I think this PR could eventually replace most of our bridge-local CDSF code.

Before switching, I see a few behavioral gaps around write safety and device compatibility that may be worth addressing:

1. Check the advertised write operation

WriteOperationMode and writeOverrunStatus currently call the HVAC writer without checking whether the remote feature advertises Write() for the corresponding function.

The underlying HVAC write helpers also write directly without checking operations. As a result, the current test fixture only advertises Read, but the write tests still succeed.

Would it make sense to return api.ErrNotSupported unless:

  • HvacSystemFunctionListData advertises Write() for operation-mode changes, and
  • HvacOverrunListData advertises Write() for start/stop?

This would also provide a reliable basis for applications that need to expose read-only versus writable controls.

2. Consider omitted changeability flags

For operation modes, a missing IsOperationModeIdChangeable currently blocks the write. The overrun path behaves differently and only blocks on an explicit false.

On the Vaillant VR940 we have seen optional changeability flags omitted even though the corresponding function advertises a write operation and accepts the write. Our compatibility rule is therefore:

advertised Write() && changeable != false

Could the operation-mode path use the same rule as the overrun path? At minimum, a test for nil, false, and true would make the intended semantics explicit.

3. Validate the mode against the DHW relation

OperationModes correctly resolves the modes through HvacSystemFunctionOperationModeRelationListData.

However, WriteOperationMode searches the global operation-mode descriptions by type and writes the first match. On an entity containing several HVAC system functions, this could select a mode ID that exists globally but is not related to the DHW system function.

Could the write path resolve the requested mode through the DHW system-function relation, just like OperationModes does?

4. Honor full-list versus partial-write semantics

Both write paths currently send a list containing only the modified entry:

Our hardware-validated VR940 path copies the complete cached list, modifies only the selected entry, and writes the complete list. We have confirmed that full-list writes work, but do not yet have evidence that a single-entry write is accepted reliably.

Would it make sense for the feature helper to:

  • use a proper partial write when WritePartial() is supported, and
  • otherwise copy and write the complete cached list?

This also avoids accidentally dropping unrelated system functions or overruns on servers that interpret the payload as a full replacement.

5. Surface the device result

The feature helpers return a message counter, but CDSF currently discards it in both write paths. This means the public method reports success after sending, even if the device later responds with a non-zero ResultData.errorNumber.

For control operations, it would be valuable either to return the message counter to the caller or to register a response callback internally and surface device rejection as an error. A successful result could also trigger a fresh read of the affected list.

Our bridge currently waits for the matching result, handles cancellation/timeout, maps non-zero error numbers to a device-rejection error, and refreshes the cache afterward. Preserving that behavior is important because the gRPC/HA caller should not see success for a rejected operation.

6. Fail closed on ambiguous IDs

As a smaller defensive improvement, systemFunctionId and overrunId currently return the first matching entry.

Unless the specification guarantees uniqueness here, returning ErrDataNotAvailable for zero or multiple matching DHW system functions/one-time-DHW overruns would avoid silently controlling the wrong entry.

Suggested additional tests would cover:

  • write operation not advertised,
  • changeability flag nil, false, and true,
  • requested mode present globally but not related to the DHW function,
  • multi-entry system-function and overrun lists,
  • non-zero device result,
  • ambiguous DHW system-function or overrun descriptions.

These points aside, the separation between MDSF and CDSF and the general package structure look like a good fit for replacing our local implementation. Thanks again for working on this!

andig and others added 2 commits July 20, 2026 17:11
…mitted changeability flag

WriteOperationMode had two write-safety gaps:

- It blocked the write whenever IsOperationModeIdChangeable was nil.
  Some devices omit the flag yet still accept the write, so only an
  explicit false should block it (matching the overrun path).
- It resolved the requested mode against the global operation-mode
  descriptions and wrote the first match, which on a multi-function
  entity could select a mode that exists globally but is not related to
  the DHW system function. Resolve the mode through the
  HvacSystemFunctionOperationModeRelation instead, mirroring
  OperationModes().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point 5: the write methods discarded the message counter and reported
success as soon as the request was sent, even if the device later
rejected it. Return the message counter and accept a resultCB so a
non-zero ResultData.ErrorNumber surfaces as a device rejection, matching
the OPEV/LPC/OHPCF write pattern.

Point 6: systemFunctionId and overrunId returned the first matching
entry. Return ErrDataNotAvailable unless exactly one DHW system function
/ one-time-DHW overrun matches, so the wrong entry is never controlled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@andig

andig commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@volschin thanks for the review. Worth noting that I have done all these with AI, while following established processes and spec. I've followed up on all points across all my open PRs. Since #239 provides the shared baseline, it would help if we could that reviewed and merged first to patch the missing pieces on the other ones.

@andig

andig commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@volschin I had Claude do a cross-check for consistency with the rest of the codebase:

1 (return api.ErrNotSupported unless the remote advertises Write()). I've added this gate to the WriteHvacSystemFunctionListData, WriteHvacOverrunListData, and WriteSetpointListData helpers. Note this is stricter than anything currently on dev — the existing write helpers (loadcontrol.WriteLimitData, deviceconfiguration.WriteKeyValues) fetch Operations()[fn] only to decide WritePartial(); none of them gate on operation.Write(). So this hardens the HVAC/Setpoint helpers beyond the current codebase behavior.

That point is implemented, too. I'm always a big fan of consistency though- if we want to do this we should do it everywhere. Do you want me to raise follow-up PR (...which would to a minor extend be breaking)?

andig and others added 3 commits July 20, 2026 20:13
The Hvac and Setpoint write helpers issued a write command without
checking whether the remote server feature advertises the Write()
operation for the corresponding function. Guard each write helper so it
returns api.ErrNotSupported unless the remote advertises Write(),
matching the existing LoadControl/DeviceConfiguration behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Hvac and Setpoint write helpers sent a list containing only the
modified entry. Servers that interpret the payload as a full replacement
would then drop unrelated system functions, overruns or setpoints.

Send a proper partial write when the remote advertises WritePartial().
Otherwise copy the cached list, merge the modified entries and write the
complete list, matching the LoadControl/DeviceConfiguration behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that the Write() advertisement gate is present in the Hvac feature
helper, the test fixture must advertise the Write operation for existing
write tests to pass, and a dedicated test verifies WriteOperationMode
returns ErrNotSupported when the remote advertises the system-function
list as read-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@andig

This comment was marked as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants