Drive the heater blower motor from the MOSFET on D2 #90
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/heater-blower-output"
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?
Adds the blower motor as a
LevelEndpoint(EP_FAN, index 13) on D2, PWM 25 kHz 8-bit, failsafe autonomous. Inmain.cpp, a rising edge on the heater's blower output requests full speed and a falling edge switches off, both with origin automation. A manual or HA command therefore holds until the heater next changes state, and the ten second lockout applies. Index 6 is unchanged.Based on #89, so this shows its commits until that merges.
Tested:
pio test -e nativeandpio run -e node_heater. Not tested on hardware.Ticket: CCS-WHP-12
Two real problems, one smaller point.
@ -1232,1 +1233,4 @@| 10 | `EP_SETTING` | Twait warm up (`0x20` byte 2) || 11 | `EP_TEMPERATURE` | Cabin temperature, from an Si7021 on I2C || 12 | `EP_HUMIDITY` | Cabin humidity, from the same Si7021 || 13 | `EP_FAN` | Blower motor on a MOSFET. Follows index 6 at full speed with origin automation, failsafe autonomous |Spec says the endpoint follows index 6, but it is a normal bus-writable Level endpoint, so a CMD_LEVEL from HA overrides it and Manual origin locks automation out for 10 s. Say that in the row (or make it non-writable), otherwise the spec and behaviour disagree.
@ -91,1 +112,3 @@node.loop();cabinSensor.loop();const uint32_t now = ctrl::nowMs();if (heater.blowerOn() != blowerWasOn) {Edge-only sync loses the edge if a request is refused. A manual command to index 13 starts the 10 s lockout; if blowerOn() flips inside it, request()/setOn() return false (commandAllowed), blowerWasOn is already updated, and the motor stays out of step with the heater until the next edge. Worst case the motor keeps running after the blower stops. Only set blowerWasOn when the call returns true, or compare against blowerMotor.isOn() each loop instead of keeping a flag.
heater.blowerOn() is never cleared on link loss (blowerOn_ is only written from a status frame, heater_endpoint.cpp:147). If the heater link drops with the blower on, the motor stays at full speed with no way to stop it from this code. Gate on the link being up, or treat link loss as blower off.
One finding on
341e4a4.@ -92,0 +115,4 @@if (blowerOn != blowerWasOn) {const bool applied =blowerOn ? blowerMotor.request(blowerMotorFullSpeed, now, can::Origin::Automation) : blowerMotor.setOn(false, now, can::Origin::Automation);if (applied) blowerWasOn = blowerOn;The retry floods the bus. While the endpoint is locked out (10 s after a manual command), request()/setOn() returns false via refuse(), which calls markDirty(). reportDue() is then true on every pass, so node.loop sends a state frame every loop iteration until the lockout ends. Fix: rate-limit the retry (e.g. only retry once the lockout has lapsed, using blowerMotor.lockedOut(now), or a short retry interval) instead of calling every loop.
Round 3: the state-frame flood is fixed. Two things left.
@ -103,1 +113,3 @@node.loop();const uint32_t now = ctrl::nowMs();const bool blowerOn = heater.blowerOn();if (blowerOn != blowerWasOn && !blowerMotor.lockedOut(now)) {Stale blower state.
HeaterEndpoint::loseLink(heater_endpoint.cpp:61) never clearsblowerOn_, so if the heater link drops while the blower runs,heater.blowerOn()stays true and the motor stays at full speed indefinitely. Drive the motor offblowerOn && !linkLost, or clearblowerOn_inloseLink. The latter also fixesHeaterBlowerInput.Edge-only sync. After a manual command the motor never realigns with the heater until the next blower edge. A manual off while the blower runs stays off. A manual on while the blower is off keeps spinning with nothing to stop it. Compare against the motor's state (
blowerMotor.isOn() != blowerOn) instead of ablowerWasOnflag; that also drops the flag and the retry logic. This glue sits inmain.cppwith no test, so the lockout-retry path is unpinned.