Repository navigation
HiveTraffic night mode: schedule, traffic gate, config and UI - #167
Merged
Merged
Conversation
A HiveTraffic counter's 48 IR emitters are its power budget — 24 series pairs behind 22 ohm ballast, all lit for the settle+read window of every 5 ms poll, roughly 0.5-1.0 A peak and an order of magnitude above the ~18 mA everything else on that board draws. European honey bees are diurnal: flight needs light, and stops below roughly 10 C regardless. So every night that current buys nothing. HiveHub now owns the schedule, because the counter cannot: it has no RTC, no NVS and no network, while this device has NTP and a DS3231 good to +/-2 ppm. Once per upload cycle the existing GATT session carries one extra 5-byte write telling the counter how long to stop sensing — no extra scan, no extra connect. The counter forgets it on any reset and it expires on its own, so a HiveHub that dies mid-night cannot leave a counter blind. The decision (firmware/include/night_mode.h) is a pure header with host tests, because it goes wrong quietly in both directions: suspend when you should not and a day's foraging becomes a row of zeros that reads as bad weather; fail to suspend and nothing happens except a flat battery three weeks later. Every uncertain path returns "keep counting" — no valid local time, no traffic baseline, a window whose ends are equal, a relay in flight. The tests cover midnight wrap, an unsynced clock, the traffic gate and a whole simulated night cycle by cycle. The traffic gate is the "not yet" rule the feature was asked for: if more than N bees crossed in the last cycle, night mode waits. It differences the previous cycle's totals, held in RTC memory across HiveHub's own deep sleep — without that there would never be a previous reading and the gate would postpone forever. A hive with no baseline is postponed rather than waved through: "I don't know how busy this is" is not "it is quiet". Timezone handling is load-bearing rather than cosmetic. The device clock is UTC (configTime(0, 0, ...)), so without a TZ a window entered as 20:00 would fire at 21:00 local in summer, discarding an hour of foraging on exactly the long evenings that have most of it. Stored as a POSIX TZ string because the ESP32 has no tz database; the dashboard offers presets. Ships OFF. Settings live in device_configs (migration 025, applied unconditionally on each config fetch rather than behind the config_version gate — that gate exists to protect portal-side calibration, and night mode has no portal counterpart), and are edited under Device & admin. Persisted in NVS so a hub that boots without WiFi still honours the last known window, which is the deployment the feature is for. The wire parser learns fw:4 — idle_s and status bit 0x80 — and keeps reading fw:2 and fw:3, since a counter reports the old revision until the OTA relay reaches it and the relay must read this characteristic first. The addition is purely additive, so the key-skipping parser already read v4 documents; what it could not do is tell a suspended counter from a broken one, which is the whole point of reading it. Also settles the interaction with the standing "relay firmware at night" advice, which deep sleep would have broken: a suspended counter stays advertising, so relays run exactly as in daylight, and HiveHub declines to suspend a counter whose relay is unfinished. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016gFpezSuXW3sHMd1xaSYnd
Both firmware builds failed to compile: Arduino's esp32-hal-gpio.h carries `#define DISABLED 0x00`, and the preprocessor does not care that the enumerator lives in an enum class inside a namespace. The macro expanded inside the enum body, the whole nightmode namespace failed to parse, and every later use cascaded into "'struct Decision' has no member named 'reason'". Rename the enumerators to CamelCase — which is the house style already (ClaimStatus) and, more to the point, is outside the all-caps namespace Arduino and ESP-IDF share. The host test could not have caught this: it compiles night_mode.h against a bare host compiler, where none of those macros exist, so it passed while the firmware build failed. Fix that too — the test now defines the Arduino macros that actually bite, with their real values, before the include. A future identifier that strays into the macro namespace fails here in seconds instead of three minutes into a firmware build. Verified by reintroducing DISABLED and confirming the test stops compiling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016gFpezSuXW3sHMd1xaSYnd
Both were missed when the feature landed. FIRMWARE_VERSION matters functionally, not just for bookkeeping: HiveHub's own OTA path compares it to decide whether a release is newer, so a device carrying night mode while still reporting 0.24.16 would not be offered the update, and nothing would confirm afterwards that it took. Every firmware behaviour change in this repo's history bumps it; this one adds a feature. SERVER_VERSION moves to 0.3.1 because /config gained fields (beecounter_night_* and timezone) and /health is where an operator reads whether their backend understands them — a device talking to an older backend simply gets the defaults and stays in the pre-night-mode behaviour, which is safe but silent. Kept to a patch bump: 0.2.0 -> 0.3.0 was a documented release with docs/releases/v0.3.md behind it, and this is one additive feature rather than a release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016gFpezSuXW3sHMd1xaSYnd
Reported as "I can't see it", and that was accurate. The fields were real and wired up, but they were a third column inside the Configuration form's grid — and that panel is a <details> closed by default, so reaching them meant expanding Configuration and scrolling past scale calibration and the tempco fit tool. Nobody looking for a bee-counter setting would look there. Move them into a top-level collapsible of their own, next to Configuration, Publish data and Admin, with their own submit handler and Save button so the panel is self-contained rather than depending on a button in a different section. No change to what is sent: the same PATCH fields, the same "start == end would never apply" guard, the same only-changed-fields patch. Mirrored into the demo dashboard, and verified by rendering it in Chromium — the summary is visible while closed, the panel opens, and the controls read back the configured window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016gFpezSuXW3sHMd1xaSYnd
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The HiveHub half of night mode. Pairs with MacNite/HiveTraffic#14, which adds the device-side suspension. Deploy order below.
Why
A HiveTraffic counter's 48 IR emitters are its power budget — 24 series pairs behind 22 Ω ballast, ~0.5–1.0 A peak, an order of magnitude above everything else on that board. European honey bees are diurnal (flight needs light, and stops below roughly 10 °C regardless), so overnight that current buys nothing.
The split
HiveHub owns the schedule because the counter cannot: it has no RTC, no NVS and no network, while this device has NTP and a DS3231 good to ±2 ppm. Once per cycle the existing GATT session carries one extra 5-byte write telling the counter how long to stop sensing — no extra scan, no extra connect. The counter forgets it on any reset and it expires on its own, so a HiveHub that dies mid-night cannot leave a counter blind.
Fails safe by construction
firmware/include/night_mode.his a pure header with host tests, because this goes wrong quietly in both directions: suspend when you should not and a day's foraging becomes a row of zeros that reads as bad weather; fail to suspend and nothing happens except a flat battery three weeks later.Every uncertain path returns keep counting — no valid local time, no traffic baseline, a window whose ends are equal, a relay in flight. The worst outcome of a bug here is a counter that counts, which is what it did before.
Two rules worth flagging as decisions rather than details:
start == endis an empty window, never a 24-hour one. The other reading would let one mis-set field stop a counter for good, an hour at a time, with nothing in the data saying why.The traffic gate
The "not yet" rule that was asked for: if more than N bees crossed in the last cycle, night mode waits. It differences the previous cycle's totals, held in RTC memory across HiveHub's own deep sleep — without that there would never be a previous reading and the gate would postpone forever. Reboots are attributed the same way
server/measurements.pydoes, so the gate and the charted interval never disagree.Timezone is load-bearing, not cosmetic
The device clock is UTC (
configTime(0, 0, …)). Without a TZ a window entered as 20:00 fires at 21:00 local in summer, discarding an hour of foraging on exactly the long evenings that have most of it. Stored as a POSIX TZ string because the ESP32 carries no tz database; the dashboard offers presets for the common zones plus free text.Scope
device_configs(migration025), edited under Device & admin.config_versiongate — that gate exists to protect a portal-side tare, and night mode has no portal counterpart. Turning the window off has to take effect next cycle, not after an unrelated edit bumps the version.Wire revision
The parser learns
fw:4(idle_s, status bit0x80) and keeps readingfw:2/fw:3. The addition is purely additive, so it already read v4 documents correctly — what it could not do is tell a suspended counter from a broken one, which is the point of reading it.The "relay at night" interaction
docs/hivetraffic-bee-counter.mdrecommends relaying firmware at night because a transfer costs counted bees. Deep sleep would have made that the one time of day a counter is unreachable. A suspended counter stays advertising, so relays run exactly as in daylight; HiveHub additionally declines to suspend a counter whose relay is unfinished.Verification
Run locally, all green:
firmware/host_test/run_tests.sh— 111 + 35 checkstest_ruuvi_decode,test_beehive_decode,test_bee_counter_wire,test_night_mode(new)test_counter_rules,test_accel_rules,test_ble_sensor_rules,test_sd_import,test_data_exporttest_tempcomp/test_hive_tempcomp/test_hiveheart_fft/test_relay_ota_gate/test_publishtest_pairing_lifecycle --require-dbagainst a real PostgreSQL 16node --checkon every dashboard/website scriptAlso verified against a live PostgreSQL, beyond what CI covers: the config round-trip (defaults off, partial patch,
config_versionbump, pre-existing fields not shifted by the new columns), the CHECK constraints, and migration 025 applied to a pre-025device_configs— twice, to prove idempotency, with the pre-existing row landing on night mode off.Not verified here: the ESP32 firmware build. The sandbox blocks the PlatformIO registry, so
firmware-buildin CI is the first compile of the firmware changes.Deploy order
This PR must ship before any counter running HiveTraffic#14, since HiveHub is what decides when to suspend and needs to understand
idle_sfirst.Generated by Claude Code