Add a measure bind that feeds a heater's measured temperature #138
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/yn-22-measure-bind"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
BindAction::Measurehands each sensor reading from a source temperature endpoint to a heater:SourceEventgains a scalar form decoded from the sensor state frame, aMeasureStrategypasses it to the newEndpoint::onMeasure, andHeaterEndpointreports it as its measured value when built with a null measured function (the function form stays for the hand-written node). When the source node goes silent for three heartbeats, the bind reports an unknown reading, which keeps the blower's full-speed fallback. The generator only emits a Measure bind on a flow5d; the runtime does not police the target type beyond picking the strategy.strategyForswitches on the target's endpoint type, with the Measure choice inside the shared on/off case anddefaultreturning null. A Mode bind has no target (it is sourced from the input it sits on), so one guard before the switch handles the null pointer. Source decoding loops over a named array of one small decoder per frame kind.MCP2515 filter: the class selection moves into
class_filter.h, which is host-testable. When the required classes plus a node's interests need more than four slots (Event and Sensor together), the mask drops the low class bit so each slot admits a pair. Extra frames are ignored as before; RXB0 is untouched. TWAI and SocketCAN are unaffected. Spec 9.5 and section 12 each get a short addition.YAML: a
flow5dtakes oneaction: measurebind from atemperatureendpoint, local ornode:remote. It is rejected on any other endpoint, from any other source type (including a temperature setting), twice on one heater, and withdisabled_modesor timing fields. A remote measure source addsSensorto the interests. The heater's measured argument isnullptrwith a measure bind and its device isheaterDemand.spaceHeating()with water heating; both compose and a test pins that. Schema, README and the heater fixture are updated.Tested: native 647, Python 140;
pio runand the heater fixture build pass.YN-22
Two small gaps, no blockers. The RXB0 invariant is untouched. The relaxed mask admits System, Alarm, Query, Event and Sensor, and no slot ends up at zero by accident. The in-place dedupe is safe, because the write index never passes the read index.
Binds::onFrame(event.scalar != (bind.action == BindAction::Measure)) and for Measure on a non-heater target returning false fromadd. Reverting the guard, or the heater check instrategyFor, fails no test.Note: Measure does not check the reading's unit or type. A mis-pointed endpoint index would feed any scalar in as a temperature. The YAML side should validate this.
29f586b34ec7b626e385Nothing blocking. Three small things.
measure, and "Outputs taketoggleandfollowbinds only" (line ~207) is now wrong for a flow5d. Add a bullet there, or fix the sentence."Measure" if bind["action"] == measureAction else bindActions[...]appears twice. Computeactiononce above thesource is Nonebranch and use it in both places. parse_binds also testsraw.get("action") == measureActionthree times, so hoist it into a local. The error "is not toggle or follow" is now incomplete.48ed902032e935f6ab63@ -25,1 +25,4 @@event.on = occupancy.occupied != 0;return event;}if (can::SensorScalar::decode(frame, event.reading)) {Surely the containing method now should be named
decodeSourceEventat this point?I'd also like to see the if blocks broken out in to methods if this method is going to be expanded upon
@ -118,2 +132,3 @@const BindStrategy* strategyFor(can::EndpointType type) {const BindStrategy* strategyFor(const Bind& bind) {if (bind.action == BindAction::Mode) return &modeStrategy;To keep it neat, put this in the
defaultcase of theswitchbelow@ -120,0 +133,4 @@const BindStrategy* strategyFor(const Bind& bind) {if (bind.action == BindAction::Mode) return &modeStrategy;const can::EndpointType type = bind.target->descriptor().type;if (bind.action == BindAction::Measure) return type == can::EndpointType::Heater ? &measureStrategy : nullptr;Turn this around so that the type check is in the switch with a body that contains the action check which returns
measureStrategyReview of
a9896a9: clean. No findings.a9896a9d2c543f07d054Clean. Nothing to act on.
@ -133,2 +145,3 @@constexpr can::EndpointType noEndpointType = static_cast<can::EndpointType>(0);const BindStrategy* strategyFor(const Bind& bind) {if (bind.action == BindAction::Mode) return &modeStrategy;I was thinking something like this:
Why has it become so complex?
@ -33,0 +39,4 @@}SourceEvent decodeSourceEvent(const can::Frame& frame) {SourceEvent event = binaryEvent(frame);Turn this list of
ifs in to a loopOne minor point, no blocker. strategyFor now accepts any action on any non-null target, so Add no longer rejects a Mode bind with a target, or a Measure bind on a Dimmer. The OnOff/Dimmer onSource has no case for them, so they do nothing. But onSourceLost still calls target->onFailsafe, so a lost source would fail-safe the target. Only a hand-written bind can hit this, since the generator emits Measure on a flow5d alone. Accepted by design, so I'm only flagging it.
@ -43,3 +42,1 @@if (!event.valid) event = occupancyEvent(frame);if (!event.valid) event = scalarEvent(frame);return event;for (SourceEvent (*decode)(const can::Frame&) : {binaryEvent, occupancyEvent, scalarEvent}) {Move this collection to the class level
@ -156,3 +154,2 @@case can::EndpointType::Heater:if (bind.action == BindAction::Measure) return &measureStrategy;return levels ? &onOffStrategy : nullptr;return bind.action == BindAction::Measure ? static_cast<const BindStrategy*>(&measureStrategy) : &onOffStrategy;Do we need the cast here?
On the Measure ternary: the cast was only there because the two branches have different pointer types, so I have replaced the ternary with two statements and the cast is gone (also: the decoder array is now a named constant beside the strategy instances). Not pushed yet, waiting for the rebase onto feat/yaml-nodes.
f1e3703b80a5b0dbf190Clean. Nothing to act on.