Skip to content

Don't require a value for inactive load control limits - #255

Open
andig wants to merge 1 commit into
enbility:devfrom
andig:fix/inactive-limit-without-value
Open

Don't require a value for inactive load control limits#255
andig wants to merge 1 commit into
enbility:devfrom
andig:fix/inactive-limit-without-value

Conversation

@andig

@andig andig commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

ConsumptionLimit/ProductionLimit bail out with ErrDataNotAvailable whenever the limit data carries no value, discarding isLimitActive and isLimitChangeable along with it.

A real Controllable System (Wolf Link heat pump module, LPC v1.0.0, scenarioSupport [1,2,3,4]) answers a loadControlLimitListData read like this:

{"loadControlLimitData":[[{"limitId":1},{"isLimitChangeable":true},{"isLimitActive":false}]]}

There is no limit in effect, so no value is stated. The information the client asks for — is a limit active? — is present and unambiguous, but the reader turns it into an error, and the Energy Guard cannot tell "no limit active" from "no data at all". Downstream this surfaces as a permanent error state for a heat pump that simply isn't being limited (evcc-io/evcc#32112, evcc-io/evcc#32126).

A value is only required to interpret an active limit. This changes the four affected readers to accept an inactive limit without a value and report it as IsActive: false, Value: 0, while an active limit without a value still returns ErrDataNotAvailable:

  • usecases/eg/lpc ConsumptionLimit
  • usecases/eg/lpp ProductionLimit
  • usecases/cs/lpc ConsumptionLimit
  • usecases/cs/lpp ProductionLimit

Other value guards in the code base were checked and are unaffected: usecases/internal/loadcontrol.go already reports the permitted maximum when a limit has no value or is inactive, and the remaining ones (ma/mpc, ma/mgcp, ma/mdt, eg/* failsafe and nominal-max readers, cem/*) read measurements, characteristics and configuration keys, where a missing value genuinely means no data.


🤖 Generated with Claude Code

@andig

andig commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Analysis against spec (Claude):

  1. SPINE 1.3.0, EEBus_SPINE_TS_LoadControl.xsd:148 — every element of LoadControlLimitDataType is minOccurs="0", value included. Omitting it is schema-legal.

  2. LPP UC TS 1.0.0, Table 23 (loadControlLimitListData @ Controllable System), on isLimitActive:

▎ "If set to "false", the limit and its timePeriod and value Elements SHALL be ignored. If set to "true", the timePeriod and value Elements SHALL be applied."

Value only carries meaning for an active limit — exactly the PR's rule. Erroring out on a missing value for an inactive limit contradicts a "SHALL be ignored".

  1. LPC HighLevel TestSpec 1.0.1 §6.11.4, Table 11 "Valid APCL messages" lists MSG_02 activated, MSG_03 deactivated, MSG_04 delete duration, MSG_05 duration as standalone messages. A loadControlLimitData carrying only isLimitActive is explicitly a valid message.

  2. LPC-TS-009/2 + 009/3 — "After a (re)start the APCL SHALL be deactivated by the CS"; also deactivated in states init / unlimited-controlled / failsafe / unlimited-autonomous. A CS never written a limit MUST report deactivated and has no value to report. Wolf case verbatim.

  3. LPP UC TS §3.4.1.2 — "Within the Initial Scenario communication, the content required by this Scenario MAY not be provided completely, but later during Runtime Scenario communication." Plus §3.4.1.3 "Partial notifications without Selectors or Elements SHALL be supported". Reader must tolerate incomplete mandatory content.

Caveat on strictness

Table 23 does mark value. 1: M at the CS. So Wolf omitting it is technically under-conformant. But mandatory-content-may-lag (point 5) + must-ignore-when-inactive (point 2) make the client's hard error the wrong reaction. PR premise holds.

@sthelen-enqs

Copy link
Copy Markdown
Contributor

I'm not sure I agree with Claude's analysis of the spec here with the partial exception of point 5. I'm not categorically against this change, but I would need to discuss within the team on if we can merge the changes to the EG as is, or if we don't think the Wolf is being spec-compliant and we might want to feature-gate this change behind a "quirks-mode" via configuration or compile time flags.

The changes to the CS usecases, though, I think are wrong in general.The EG is not allowed to delete value or send a full write without value set. Any partial write will get merged by the SPINE layer and should never show up to the eebus-go layer without value set. If it does, that write either should have been rejected or was incorrectly merged. This would be a bug elsewhere in the stacks and should be fixed there. If the issue is that a CS never set an initial limit then we should enforce that it must or provide a suitable default value so that implementations using eebus-go are always spec-compliant.

For the points from Claude's analysis:

  1. Basically everything in SPINE is listed as minOccurs=0 even if it's mandatory because SPINE is simultaneously describing a data model and a message format which can be used to transfer parts of the data module and it does this using the same schemas/schema files. Trusting what the schemas say is a good way of shooting yourself in the foot.
  2. true, but doesn't define whether or not they must be present anyway
  3. This only applies to messages, but not to the data model. Even if a message does not contain a value doesn't mean the data model does not contain the value. This part of the test spec concerns itself with writes and notifications using RFE that expect that client and server have a shared view of the underlying data model containing all values not contained in the message.
  4. Not really relevant, limit value is set as mandatory and must be set even if isActive is false and the limit will be ignored. It is very possible that an EG sends a partial write only containing isActive: true, then what is the CS meant to do?
    5a. The first part §3.4.1.2 is potentially valid and I'll discuss this with the rest of the team. The outcome of that discussion will determine whether we can merge this PR wholly, without the CS changes, or only in/for a "quirks" mode which allows communication with spec incompatible devices.
    5b. The second part §3.4.1.3 is irrelevant to this PR and the issue. notifications are always merged with the current known state in the spine layer and never surface unmerged to the API layer

tl;dr I will discuss this issue with the team and we will come to a decision on how we want to move forward with this

Thank you for bringing this issue to our attention

@andig

andig commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

The EG is not allowed to delete value or send a full write without value set.

@sthelen-enqs you know the spec mich better than me. It seems to me though that it is just not very clear. Not setting a value limit for inactivating a limit seems permissible as the value doesn't matter anyway. This is an in-the-wild experience from a Wolf heatpump, so whoever read the spec on the other side has done it that way.

5b. The second part §3.4.1.3 is irrelevant to this PR and the issue. notifications are always merged with the current known state in the spine layer and never surface unmerged to the API layer

I don't know if a disable message could be the first one received, if it is it might be incomplete if it disables. May be academic.

It is very possible that an EG sends a partial write only containing isActive: true, then what is the CS meant to do?

Either this should be an error as it is ambiguous OR as valid when merged with existing data. This is however specifically not the case I've brought to discussion here.

I guess this comes down to the question if the interpretation should be hard or forgiving. I'm happy with any decision.

Basically everything in SPINE is listed as minOccurs=0 even if it's mandatory because SPINE is simultaneously describing a data model and a message format which can be used to transfer parts of the data module and it does this using the same schemas/schema files

Yes, fubar...

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