Light the bathroom at a stored level in night mode #71
Loading…
Reference in a new issue
No description provided.
Delete branch "bathroom-night-brightness"
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?
CCS-LLA-26. Occupancy in
MODE_NIGHTnow brings the bathroom light on at a stored night level rather than full brightness; any other mode still brings it on at full. Both go throughLevelEndpoint::requestso the manual lockout still applies, and a mode change on its own never dims or brightens an already-lit circuit.The night level is a new
SettingEndpointon the lighting node (index 7, range 0-1000, default 300), which needed a newSemanticClass::Level(0x2C, 0.1 %) added to the protocol and its band table indocs/can-protocol.mdsection 5.2.The fan half of this ticket (suppressing the extractor in night mode) was already done by CCS-LLA-20; nothing changes on the bathroom node here.
Tested with
pio test -e native(447 cases, all passing, including the shared test vectors) andpio run -e node_lighting.Design (nullable pointer default),
request()vssetOn()lockout/origin handling, theSemanticClass::Leveladdition, and the spec/test-vector updates all check out. Two things worth a look before merge, plus one bit of drive-by formatting noise.@ -66,14 +67,14 @@ ctrl::TouchInput touchSwitch(epTouch, touchPin, touchReportIntervalDs);// threshold set below.ctrl::PreferencesStore settingsStore("lighting");ctrl::SettingEndpoint touchEnable(epTouchEnable, can::SemanticClass::Enablement, 0, 1, 1, settingsStore, "touchEnable");ctrl::SettingEndpoint bathroomNightLevel(epBathroomNightLevel, can::SemanticClass::Level, 0, 1000, 300, settingsStore, "bathNightLevel");bathroomNightLevelallows 0 as a valid, in-range setting. If it's ever set to 0 (HA slider, CMD_SETTING, whatever),levelForreturns 0 for night mode, andLevelEndpoint::request(0, ...)treats 0 as "switch off" rather than "very dim" (see the comment inlevel_endpoint.cpp: "A level of zero switches off"). So a night-level of 0 silently defeats the whole feature — occupancy in night mode would never light the room. Worth raisingminValueto 1, or guarding inlevelFor/turnOn, so a real setting value can't collide with the off sentinel.@ -74,3 +76,2 @@void onTouch() {if (!touchEnable.value() || node.mode() == can::mode::travel)return;if (!touchEnable.value() || node.mode() == can::mode::travel) return;Unrelated formatting change: collapsing this
if (...) \n return;onto one line is drive-by churn, not part of this feature.@ -476,0 +494,4 @@TEST_ASSERT_EQUAL_UINT16(300, light.lastLevel);}void test_normal_occupancy_requests_full_brightness() {The two new tests only cover the off→on transition in a fixed mode. There's no test locking in the stated requirement that a mode change alone, while the light is already lit, must not re-level it (the
!light_.isOn()guard handles this, but nothing exercises it withnightLevel_wired up). Worth adding a case: turn on in normal mode (full), flip mode to night while still occupied, and assertlastLevel/onsare unchanged.Re-review of
48642b8. All three prior findings are actually fixed:minValue_is now 1 onbathroomNightLevel, and it's broadcast in the descriptor (Endpoint::minValue_/maxValue_), so a consumer genuinely sees the 1-1000 range, not just this node's internal clamp. TracedLevelEndpoint::request(): a clamped value of 1 setsbrightness_ = 1, on_ = true, so it can never collide with the level-0-means-off path.test_entering_night_does_not_relevel_a_lit_roomis a real regression lock: it exploits thatBathroomPolicy::onOccupancyonly callsturnOnwhen!light_.isOn(), matches howmain.cppactually drives it (mode is read live vian.mode()on each PIR report, there's no separate "mode changed" event), and would fail if that guard were ever removed.onTouch()two-lineif/returnnow matchesmainbyte-for-byte — confirmed by diffing againstmain. Not a real fix so much as a non-issue: running today's clang-format (18.1.3) overmain's own copy of that line also collapses it to one line, so the "collapse" was pre-existing drift between clang-format versions, not something this branch introduced. Nothing to do here.Full suite:
pio test -e native(448/448),pio run -e node_lighting(builds). Ran clang-format --dry-run over every changed file; only the one already-explained line differs from current clang-format output, and it matchesmain.One pre-existing thing, not a blocker:
SettingEndpoint's constructor storesdefaultValue(andbegin()'s fallback) without clamping it to[minValue_, maxValue_]. Doesn't bite here sincebathroomNightLevel's default is 300, safely inside 1-1000, but the class itself would silently accept a future construction with an out-of-range default and hand back that value frombegin(). Not part of this PR's diff, just flagging for whoever next touchessetting_endpoint.cpp.Third pass, focused on
3bdff0b. CI on3bdff0bis green (build job succeeded, release skipped as expected for a PR).The plumbing change itself is fine:
BathroomPolicyno longer holds aSettingEndpoint*,nightLevelis a plainuint16_tparameter, andmain.cppreadsbathroomNightLevel.value()fresh on eachonFramecall — no staleness risk, sincevalue()is just the in-memory field set synchronously byonCommand/requeston the same thread. This matchesExtractorPolicy::update's existing pattern of passing tunables by value. Spot-checked a representative sample of the ~34 touchedonOccupancy/run()call sites against main (occupancy toggles, away/travel suppression, refusal/retry, clock-wrap, the three night-level tests) — only the argument list changed, no assertions or surrounding logic moved. Therun()helper's newnightLevel = 1000parameter is correctly the last one, aftermode, and threaded straight into the loop body'spolicy.onOccupancycall.minValue = 1onbathroomNightLeveland the mode-hold test (test_entering_night_does_not_relevel_a_lit_room) are both still present and unregressed; no drive-by formatting has crept back in for any file this commit touched.One pre-existing item, not introduced by
3bdff0bbut still live on this branch:src/node_lighting/main.cpp:79, from48642b8—clang-format --dry-run -Werrorflags this; the project's style collapses a single-statementifbody onto one line (as done elsewhere in this same diff, e.g.if (!light_.isOn()) light_.turnOn(...)). Worth aclang-format -ipass before merge since CONTRIBUTING.md/CLAUDE.md calls that step out as manual.Nothing else outstanding.
Re the
onTouch()two-lineif/returnatsrc/node_lighting/main.cpp:79flagged in the last review: leaving it as-is. It's byte-for-byte identical tomain(git diff origin/main -- src/node_lighting/main.cppshows no change on that line) — the collapse-to-one-line that clang-format 18.1.3 wants is pre-existing drift onmainitself, not something this PR introduces or should fix. Collapsing it here would be an unrelated formatting change riding along on this ticket's diff. Out of scope for CCS-LLA-26.Everything else from this and the two earlier review passes is addressed. CI is green on
3bdff0b.