Merge remote-tracking branch 'origin/keymindCascade' into keymindCascade

This commit is contained in:
mikecarper committed 2026-10-17 11:22:33 -07:00
commit 80f7b17d89
24 files changed
+287 -134

No files matched your search

+4
View File
@@ -81,6 +81,10 @@ AGC resets still request recalibration.
Dual-profile scanning keeps its existing timing and calibration policy.
ESP32 timed sleep uses a millisecond timer capped at the next sample deadline.
Repeater, Room and Sensor also preserve millisecond queue, retry, RS232 standby,
scheduled-radio and maintenance deadlines, so the last fraction of a second
does not become a busy-wait tail. A future sub-second deadline is not reported
as blocked work; a deadline that is already due still prevents sleep.
nRF52, RP2040 and STM32 retain their interrupt-idle wake paths. Other sleep
guards, including USB/logging, packet queues and bridges, still apply.
These changes have host scheduling tests and firmware build coverage; their
+36 -35
View File
@@ -5148,82 +5148,83 @@ bool MyMesh::hasScheduledRadioWorkDue() const {
|| millisHasNowPassed(next_scheduled_radio_check_at));
}
uint32_t MyMesh::limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_secs) const {
if (!timestamp || sleep_secs == 0) {
return sleep_secs;
uint32_t MyMesh::limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_ms) const {
if (!timestamp || sleep_ms == 0) {
return sleep_ms;
}
const uint32_t now = millis();
if ((int32_t)(now - (uint32_t)timestamp) >= 0) {
return 0;
}
const uint32_t remaining_ms = (uint32_t)timestamp - now;
uint32_t remaining_secs = mesh::power::powerSaveSleepSeconds(remaining_ms);
return remaining_secs < sleep_secs ? remaining_secs : sleep_secs;
return remaining_ms < sleep_ms ? remaining_ms : sleep_ms;
}
uint32_t MyMesh::limitSleepToScheduledRadioWork(uint32_t sleep_secs) const {
sleep_secs = limitSleepToMillisTimer(next_scheduled_radio_check_at, sleep_secs);
uint32_t MyMesh::limitSleepToScheduledRadioWork(uint32_t sleep_ms) const {
sleep_ms = limitSleepToMillisTimer(next_scheduled_radio_check_at, sleep_ms);
const uint64_t current_uptime_millis =
uptime_millis + (uint32_t)(millis() - last_millis);
for (int i = 0; i < MAX_SCHEDULED_RADIO_SETTINGS; i++) {
const ScheduledRadioSetting& setting = scheduled_radio_settings[i];
if (!setting.active || !setting.temporary) continue;
const uint32_t hard_end_seconds =
mesh::TempRadioLeaseDeadline::secondsUntil(
const uint32_t hard_end_ms =
mesh::TempRadioLeaseDeadline::millisecondsUntil(
current_uptime_millis, setting.hard_end_uptime_millis);
if (hard_end_seconds < sleep_secs) sleep_secs = hard_end_seconds;
if (hard_end_ms < sleep_ms) sleep_ms = hard_end_ms;
}
if (scheduled_radio_retry_at && !millisHasNowPassed(scheduled_radio_retry_at)) {
sleep_secs = limitSleepToMillisTimer(scheduled_radio_retry_at, sleep_secs);
sleep_ms = limitSleepToMillisTimer(scheduled_radio_retry_at, sleep_ms);
}
return sleep_secs;
return sleep_ms;
}
uint32_t MyMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const {
return mesh::power::powerSaveSleepSeconds(getPowerSaveSleepMilliseconds(
mesh::power::secondsToSleepMilliseconds(max_secs)));
}
uint32_t MyMesh::getPowerSaveSleepMilliseconds(uint32_t max_ms) const {
using mesh::powerdiag::mask;
using mesh::powerdiag::Reason;
const mesh::powerdiag::Mask pending = max_secs ? pendingWorkReason() : mask(Reason::NoWake);
const mesh::powerdiag::Mask pending = max_ms ? pendingWorkReason() : mask(Reason::NoWake);
if (pending) {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(pending, millis());
return 0;
}
mesh::powerdiag::Mask blocked = 0;
uint32_t sleep_secs = max_secs;
uint32_t sleep_ms = max_ms;
uint32_t queue_delay_ms;
if (getNextQueueWakeDelay(queue_delay_ms)) {
uint32_t queue_delay_secs = mesh::power::powerSaveSleepSeconds(queue_delay_ms);
if (queue_delay_secs < sleep_secs) sleep_secs = queue_delay_secs;
if (queue_delay_secs == 0) blocked |= mask(Reason::Queue);
if (queue_delay_ms < sleep_ms) sleep_ms = queue_delay_ms;
if (queue_delay_ms == 0) blocked |= mask(Reason::Queue);
}
uint32_t retry_delay_ms;
if (getNextRetryWakeDelay(retry_delay_ms)) {
uint32_t retry_delay_secs = mesh::power::powerSaveSleepSeconds(retry_delay_ms);
if (retry_delay_secs < sleep_secs) sleep_secs = retry_delay_secs;
if (retry_delay_secs == 0) blocked |= mask(Reason::Retry);
if (retry_delay_ms < sleep_ms) sleep_ms = retry_delay_ms;
if (retry_delay_ms == 0) blocked |= mask(Reason::Retry);
}
const uint32_t before_scheduled = sleep_secs;
sleep_secs = limitSleepToMillisTimer(next_flood_advert, sleep_secs);
sleep_secs = limitSleepToMillisTimer(next_local_advert, sleep_secs);
sleep_secs = limitSleepToMillisTimer(dirty_contacts_expiry, sleep_secs);
sleep_secs = limitSleepToMillisTimer(next_recent_repeater_sweep, sleep_secs);
const uint32_t before_scheduled = sleep_ms;
sleep_ms = limitSleepToMillisTimer(next_flood_advert, sleep_ms);
sleep_ms = limitSleepToMillisTimer(next_local_advert, sleep_ms);
sleep_ms = limitSleepToMillisTimer(dirty_contacts_expiry, sleep_ms);
sleep_ms = limitSleepToMillisTimer(next_recent_repeater_sweep, sleep_ms);
if (_prefs.battery_alert_enabled) {
sleep_secs = limitSleepToMillisTimer(next_battery_alert_check, sleep_secs);
sleep_ms = limitSleepToMillisTimer(next_battery_alert_check, sleep_ms);
}
sleep_secs = limitSleepToScheduledRadioWork(sleep_secs);
if (before_scheduled > 0 && sleep_secs == 0) blocked |= mask(Reason::Scheduled);
sleep_ms = limitSleepToScheduledRadioWork(sleep_ms);
if (before_scheduled > 0 && sleep_ms == 0) blocked |= mask(Reason::Scheduled);
#if defined(WITH_RS232_BRIDGE)
if (bridge && rs232BridgeEnabled()) {
// Timed sleepers must not overshoot; interrupt-idle boards can still
// sleep with a positive sub-second remainder and wake on their clock IRQ.
const uint32_t bridge_sleep_secs = mesh::power::powerSaveSleepSeconds(
bridge->powerWakeDelayMs(millis()));
if (bridge_sleep_secs < sleep_secs) sleep_secs = bridge_sleep_secs;
if (bridge_sleep_secs == 0) blocked |= mask(Reason::BridgeWake);
// Preserve the exact rendezvous deadline; the board selects timed sleep
// or interrupt idle without rounding away its sub-second remainder.
const uint32_t bridge_sleep_ms = bridge->powerWakeDelayMs(millis());
if (bridge_sleep_ms < sleep_ms) sleep_ms = bridge_sleep_ms;
if (bridge_sleep_ms == 0) blocked |= mask(Reason::BridgeWake);
}
#endif
if (blocked && mesh::powerdiag::enabled()) mesh::powerdiag::decision(blocked, millis());
return sleep_secs;
return sleep_ms;
}
void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins, uint16_t preamble) {
+3 -2
View File
@@ -942,8 +942,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
void formatClockSyncTable(char* reply, size_t reply_len) const;
void formatClockSyncSampleDetail(int index, char* reply, size_t reply_len) const;
bool hasScheduledRadioWorkDue() const;
uint32_t limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_secs) const;
uint32_t limitSleepToScheduledRadioWork(uint32_t sleep_secs) const;
uint32_t limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_ms) const;
uint32_t limitSleepToScheduledRadioWork(uint32_t sleep_ms) const;
bool hasStartedScheduledTempRadio() const;
int findFreeScheduledRadioSlot() const;
int countScheduledRadioSettings(bool temporary) const;
@@ -1236,6 +1236,7 @@ public:
#endif
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs) const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t max_ms) const;
#if defined(WITH_BRIDGE)
+9 -9
View File
@@ -646,29 +646,29 @@ void loop() {
can_power_save = can_power_save && sleep_guard(!user_btn.needsPolling(), mesh::powerdiag::Reason::Button);
#endif
if (can_power_save) {
uint32_t sleep_secs = the_mesh.getPowerSaveSleepSeconds(30);
uint32_t sleep_ms = the_mesh.getPowerSaveSleepMilliseconds(30000);
uint32_t noise_floor_delay_ms = 0;
const bool noise_floor_wait = the_mesh.getNextNoiseFloorWakeDelay(noise_floor_delay_ms);
if (noise_floor_wait && noise_floor_delay_ms < sleep_ms) sleep_ms = noise_floor_delay_ms;
if (noise_floor_wait && noise_floor_delay_ms == 0) {
if (mesh::powerdiag::enabled())
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Calibration), millis());
sleep_secs = 0;
sleep_ms = 0;
}
#ifdef HAS_EXTERNAL_WATCHDOG
if (sleep_secs > 0) external_watchdog.feed();
if (sleep_ms > 0) external_watchdog.feed();
#endif
#if defined(NRF52_PLATFORM)
if (sleep_secs > 0) {
if (sleep_ms > 0) {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
if (noise_floor_wait) board.sleepMilliseconds(mesh::power::limitSleepMilliseconds(sleep_secs, noise_floor_delay_ms));
if (noise_floor_wait) board.sleepMilliseconds(sleep_ms);
else board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
}
#else
if (sleep_secs > 0 && the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
if (sleep_ms > 0 && the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
if (noise_floor_wait) board.sleepMilliseconds(mesh::power::limitSleepMilliseconds(sleep_secs, noise_floor_delay_ms));
else board.sleep(sleep_secs); // Wake for scheduled jobs or a LoRa packet.
} else if (sleep_secs > 0 && mesh::powerdiag::enabled()) {
board.sleepMilliseconds(sleep_ms); // Wake for scheduled jobs or a LoRa packet.
} else if (sleep_ms > 0 && mesh::powerdiag::enabled()) {
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Startup), millis());
}
#endif
+25 -23
View File
@@ -4186,59 +4186,61 @@ bool MyMesh::isMillisTimerDue(unsigned long timestamp) const {
return timestamp && millisHasNowPassed(timestamp);
}
uint32_t MyMesh::limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_secs) const {
if (!timestamp || sleep_secs == 0) {
return sleep_secs;
uint32_t MyMesh::limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_ms) const {
if (!timestamp || sleep_ms == 0) {
return sleep_ms;
}
const uint32_t now = millis();
if ((int32_t)(now - (uint32_t)timestamp) >= 0) {
return 0;
}
const uint32_t remaining_ms = (uint32_t)timestamp - now;
uint32_t remaining_secs = mesh::power::powerSaveSleepSeconds(remaining_ms);
return remaining_secs < sleep_secs ? remaining_secs : sleep_secs;
return remaining_ms < sleep_ms ? remaining_ms : sleep_ms;
}
uint32_t MyMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const {
return mesh::power::powerSaveSleepSeconds(getPowerSaveSleepMilliseconds(
mesh::power::secondsToSleepMilliseconds(max_secs)));
}
uint32_t MyMesh::getPowerSaveSleepMilliseconds(uint32_t max_ms) const {
using mesh::powerdiag::mask;
using mesh::powerdiag::Reason;
const mesh::powerdiag::Mask pending = max_secs ? pendingWorkReason() : mask(Reason::NoWake);
const mesh::powerdiag::Mask pending = max_ms ? pendingWorkReason() : mask(Reason::NoWake);
if (pending) {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(pending, millis());
return 0;
}
mesh::powerdiag::Mask blocked = 0;
uint32_t sleep_secs = max_secs;
uint32_t sleep_ms = max_ms;
uint32_t queue_delay_ms;
if (getNextQueueWakeDelay(queue_delay_ms)) {
uint32_t queue_delay_secs = mesh::power::powerSaveSleepSeconds(queue_delay_ms);
if (queue_delay_secs < sleep_secs) sleep_secs = queue_delay_secs;
if (queue_delay_secs == 0) blocked |= mask(Reason::Queue);
if (queue_delay_ms < sleep_ms) sleep_ms = queue_delay_ms;
if (queue_delay_ms == 0) blocked |= mask(Reason::Queue);
}
uint32_t retry_delay_ms;
if (getNextRetryWakeDelay(retry_delay_ms)) {
uint32_t retry_delay_secs = mesh::power::powerSaveSleepSeconds(retry_delay_ms);
if (retry_delay_secs < sleep_secs) sleep_secs = retry_delay_secs;
if (retry_delay_secs == 0) blocked |= mask(Reason::Retry);
if (retry_delay_ms < sleep_ms) sleep_ms = retry_delay_ms;
if (retry_delay_ms == 0) blocked |= mask(Reason::Retry);
}
const uint32_t before_scheduled = sleep_secs;
const uint32_t before_scheduled = sleep_ms;
if (acl.getNumClients() > 0) {
sleep_secs = limitSleepToMillisTimer(next_push, sleep_secs);
sleep_ms = limitSleepToMillisTimer(next_push, sleep_ms);
}
sleep_secs = limitSleepToMillisTimer(next_flood_advert, sleep_secs);
sleep_secs = limitSleepToMillisTimer(next_local_advert, sleep_secs);
sleep_ms = limitSleepToMillisTimer(next_flood_advert, sleep_ms);
sleep_ms = limitSleepToMillisTimer(next_local_advert, sleep_ms);
const bool radio_apply_backoff = radio_apply_retry_at && !millisHasNowPassed(radio_apply_retry_at);
if (radio_apply_backoff) {
sleep_secs = limitSleepToMillisTimer(radio_apply_retry_at, sleep_secs);
sleep_ms = limitSleepToMillisTimer(radio_apply_retry_at, sleep_ms);
} else {
sleep_secs = limitSleepToMillisTimer(set_radio_at, sleep_secs);
sleep_secs = limitSleepToMillisTimer(revert_radio_at, sleep_secs);
sleep_ms = limitSleepToMillisTimer(set_radio_at, sleep_ms);
sleep_ms = limitSleepToMillisTimer(revert_radio_at, sleep_ms);
}
sleep_secs = limitSleepToMillisTimer(dirty_contacts_expiry, sleep_secs);
if (before_scheduled > 0 && sleep_secs == 0) blocked |= mask(Reason::Scheduled);
sleep_ms = limitSleepToMillisTimer(dirty_contacts_expiry, sleep_ms);
if (before_scheduled > 0 && sleep_ms == 0) blocked |= mask(Reason::Scheduled);
if (blocked && mesh::powerdiag::enabled()) mesh::powerdiag::decision(blocked, millis());
return sleep_secs;
return sleep_ms;
}
// To check if there is pending work
+2 -1
View File
@@ -1041,6 +1041,7 @@ public:
void buildStatsJson(char* buf, size_t buf_size) override;
#endif
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs) const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t max_ms) const;
// To check if there is pending work
bool hasPendingWork() const;
@@ -1048,5 +1049,5 @@ public:
private:
bool isMillisTimerDue(unsigned long timestamp) const;
uint32_t limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_secs) const;
uint32_t limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_ms) const;
};
+9 -9
View File
@@ -418,29 +418,29 @@ void loop() {
can_power_save = can_power_save && sleep_guard(!user_btn.needsPolling(), mesh::powerdiag::Reason::Button);
#endif
if (can_power_save) {
uint32_t sleep_secs = the_mesh.getPowerSaveSleepSeconds(30);
uint32_t sleep_ms = the_mesh.getPowerSaveSleepMilliseconds(30000);
uint32_t noise_floor_delay_ms = 0;
const bool noise_floor_wait = the_mesh.getNextNoiseFloorWakeDelay(noise_floor_delay_ms);
if (noise_floor_wait && noise_floor_delay_ms < sleep_ms) sleep_ms = noise_floor_delay_ms;
if (noise_floor_wait && noise_floor_delay_ms == 0) {
if (mesh::powerdiag::enabled())
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Calibration), millis());
sleep_secs = 0;
sleep_ms = 0;
}
#ifdef HAS_EXTERNAL_WATCHDOG
if (sleep_secs > 0) external_watchdog.feed();
if (sleep_ms > 0) external_watchdog.feed();
#endif
#if defined(NRF52_PLATFORM)
if (sleep_secs > 0) {
if (sleep_ms > 0) {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
if (noise_floor_wait) board.sleepMilliseconds(mesh::power::limitSleepMilliseconds(sleep_secs, noise_floor_delay_ms));
if (noise_floor_wait) board.sleepMilliseconds(sleep_ms);
else board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
}
#else
if (sleep_secs > 0 && the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
if (sleep_ms > 0 && the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
if (noise_floor_wait) board.sleepMilliseconds(mesh::power::limitSleepMilliseconds(sleep_secs, noise_floor_delay_ms));
else board.sleep(sleep_secs); // Wake for scheduled jobs or a LoRa packet.
} else if (sleep_secs > 0 && mesh::powerdiag::enabled()) {
board.sleepMilliseconds(sleep_ms); // Wake for scheduled jobs or a LoRa packet.
} else if (sleep_ms > 0 && mesh::powerdiag::enabled()) {
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Startup), millis());
}
#endif
+31 -30
View File
@@ -1702,16 +1702,15 @@ bool SensorMesh::isMillisTimerDue(unsigned long timestamp) const {
}
uint32_t SensorMesh::limitSleepToMillisTimer(unsigned long timestamp,
uint32_t sleep_secs) const {
if (!timestamp || sleep_secs == 0) return sleep_secs;
uint32_t sleep_ms) const {
if (!timestamp || sleep_ms == 0) return sleep_ms;
const uint32_t now = millis();
if ((int32_t)(now - (uint32_t)timestamp) >= 0) return 0;
// Keep the millis arithmetic 32-bit, including on native test hosts.
const uint32_t remaining_ms = (uint32_t)timestamp - now;
uint32_t remaining_secs = mesh::power::powerSaveSleepSeconds(remaining_ms);
return remaining_secs < sleep_secs ? remaining_secs : sleep_secs;
return remaining_ms < sleep_ms ? remaining_ms : sleep_ms;
}
bool SensorMesh::hasPendingWork() const {
@@ -1753,16 +1752,21 @@ mesh::powerdiag::Mask SensorMesh::pendingWorkReason() const {
}
uint32_t SensorMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const {
return mesh::power::powerSaveSleepSeconds(getPowerSaveSleepMilliseconds(
mesh::power::secondsToSleepMilliseconds(max_secs)));
}
uint32_t SensorMesh::getPowerSaveSleepMilliseconds(uint32_t max_ms) const {
using mesh::powerdiag::mask;
using mesh::powerdiag::Reason;
const mesh::powerdiag::Mask pending = max_secs ? pendingWorkReason() : mask(Reason::NoWake);
const mesh::powerdiag::Mask pending = max_ms ? pendingWorkReason() : mask(Reason::NoWake);
if (pending) {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(pending, millis());
return 0;
}
mesh::powerdiag::Mask blocked = 0;
uint32_t sleep_secs = max_secs;
uint32_t sleep_ms = max_ms;
#if MESH_ENABLE_SENSOR_TRACKER
if (tracker.enabled && !tracker_storage_fault) {
const uint32_t now = getRTCClock()->getCurrentTime();
@@ -1770,56 +1774,53 @@ uint32_t SensorMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(mask(Reason::Tracker), millis());
return 0;
}
const uint32_t until_check = tracker_next_check - now;
if (until_check < sleep_secs) sleep_secs = until_check;
const uint32_t until_check = mesh::power::secondsToSleepMilliseconds(tracker_next_check - now);
if (until_check < sleep_ms) sleep_ms = until_check;
}
#endif
uint32_t wake_delay_ms;
if (getNextQueueWakeDelay(wake_delay_ms)) {
uint32_t wake_delay_secs = mesh::power::powerSaveSleepSeconds(wake_delay_ms);
if (wake_delay_secs < sleep_secs) sleep_secs = wake_delay_secs;
if (wake_delay_secs == 0) blocked |= mask(Reason::Queue);
if (wake_delay_ms < sleep_ms) sleep_ms = wake_delay_ms;
if (wake_delay_ms == 0) blocked |= mask(Reason::Queue);
}
#if MESH_ENABLE_SENSOR_TRACKER && MESH_ENABLE_TX_BEACONS
// The normal queue query omits RF maintenance while radio_available is
// false. A tracker's deliberate RF sleep must not hide its next beacon.
if (tracker.enabled && getNextBeaconWakeDelay(wake_delay_ms)) {
uint32_t wake_delay_secs = mesh::power::powerSaveSleepSeconds(wake_delay_ms);
if (wake_delay_secs < sleep_secs) sleep_secs = wake_delay_secs;
if (wake_delay_secs == 0) blocked |= mask(Reason::Queue);
if (wake_delay_ms < sleep_ms) sleep_ms = wake_delay_ms;
if (wake_delay_ms == 0) blocked |= mask(Reason::Queue);
}
#endif
if (getNextRetryWakeDelay(wake_delay_ms)) {
uint32_t wake_delay_secs = mesh::power::powerSaveSleepSeconds(wake_delay_ms);
if (wake_delay_secs < sleep_secs) sleep_secs = wake_delay_secs;
if (wake_delay_secs == 0) blocked |= mask(Reason::Retry);
if (wake_delay_ms < sleep_ms) sleep_ms = wake_delay_ms;
if (wake_delay_ms == 0) blocked |= mask(Reason::Retry);
}
const uint32_t before_scheduled = sleep_secs;
sleep_secs = limitSleepToMillisTimer(next_flood_advert, sleep_secs);
sleep_secs = limitSleepToMillisTimer(next_local_advert, sleep_secs);
sleep_secs = limitSleepToMillisTimer(dirty_contacts_expiry, sleep_secs);
const uint32_t before_scheduled = sleep_ms;
sleep_ms = limitSleepToMillisTimer(next_flood_advert, sleep_ms);
sleep_ms = limitSleepToMillisTimer(next_local_advert, sleep_ms);
sleep_ms = limitSleepToMillisTimer(dirty_contacts_expiry, sleep_ms);
if (num_alert_tasks > 0) {
sleep_secs = limitSleepToMillisTimer(alert_tasks[0]->send_expiry,
sleep_secs);
sleep_ms = limitSleepToMillisTimer(alert_tasks[0]->send_expiry,
sleep_ms);
}
const bool radio_apply_backoff = radio_apply_retry_at
&& !millisHasNowPassed(radio_apply_retry_at);
if (radio_apply_backoff) {
sleep_secs = limitSleepToMillisTimer(radio_apply_retry_at, sleep_secs);
sleep_ms = limitSleepToMillisTimer(radio_apply_retry_at, sleep_ms);
} else {
sleep_secs = limitSleepToMillisTimer(set_radio_at, sleep_secs);
sleep_secs = limitSleepToMillisTimer(revert_radio_at, sleep_secs);
sleep_ms = limitSleepToMillisTimer(set_radio_at, sleep_ms);
sleep_ms = limitSleepToMillisTimer(revert_radio_at, sleep_ms);
}
uint32_t now_secs = getRTCClock()->getCurrentTime();
uint32_t sensor_due = last_read_time + SENSOR_READ_INTERVAL_SECS;
if (now_secs < sensor_due) {
uint32_t sensor_delay = sensor_due - now_secs;
if (sensor_delay < sleep_secs) sleep_secs = sensor_delay;
uint32_t sensor_delay = mesh::power::secondsToSleepMilliseconds(sensor_due - now_secs);
if (sensor_delay < sleep_ms) sleep_ms = sensor_delay;
}
if (before_scheduled > 0 && sleep_secs == 0) blocked |= mask(Reason::Scheduled);
if (before_scheduled > 0 && sleep_ms == 0) blocked |= mask(Reason::Scheduled);
if (blocked && mesh::powerdiag::enabled()) mesh::powerdiag::decision(blocked, millis());
return sleep_secs;
return sleep_ms;
}
+2 -1
View File
@@ -87,6 +87,7 @@ public:
const char* getNodeName() { return _prefs.node_name; }
NodePrefs* getNodePrefs() { return &_prefs; }
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs) const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t max_ms) const;
bool isTrackerModeEnabled() const {
#if MESH_ENABLE_SENSOR_TRACKER
return tracker.enabled;
@@ -299,7 +300,7 @@ private:
bool applySavedRadioParams();
bool isMillisTimerDue(unsigned long timestamp) const;
uint32_t limitSleepToMillisTimer(unsigned long timestamp,
uint32_t sleep_secs) const;
uint32_t sleep_ms) const;
bool hasPendingWork() const;
mesh::powerdiag::Mask pendingWorkReason() const;
bool region_load_active;
+11 -11
View File
@@ -384,23 +384,24 @@ void loop() {
can_power_save = can_power_save && sleep_guard(!user_btn.needsPolling(), mesh::powerdiag::Reason::Button);
#endif
if (can_power_save) {
uint32_t sleep_secs = the_mesh.getPowerSaveSleepSeconds(30);
uint32_t sleep_ms = the_mesh.getPowerSaveSleepMilliseconds(30000);
uint32_t noise_floor_delay_ms = 0;
const bool noise_floor_wait = the_mesh.getNextNoiseFloorWakeDelay(noise_floor_delay_ms);
if (noise_floor_wait && noise_floor_delay_ms < sleep_ms) sleep_ms = noise_floor_delay_ms;
if (noise_floor_wait && noise_floor_delay_ms == 0) {
if (mesh::powerdiag::enabled())
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Calibration), millis());
sleep_secs = 0;
sleep_ms = 0;
}
#ifdef HAS_EXTERNAL_WATCHDOG
if (sleep_secs > 0) external_watchdog.feed();
if (sleep_ms > 0) external_watchdog.feed();
#endif
#if defined(NRF52_PLATFORM)
if (sleep_secs > 0) {
if (sleep_ms > 0) {
#if MESH_ENABLE_SENSOR_TRACKER
if (!the_mesh.isTrackerModeEnabled() || armTrackerSleepWake(sleep_secs)) {
if (!the_mesh.isTrackerModeEnabled() || armTrackerSleepWake(mesh::power::powerSaveSleepSeconds(sleep_ms))) {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
if (noise_floor_wait) board.sleepMilliseconds(mesh::power::limitSleepMilliseconds(sleep_secs, noise_floor_delay_ms));
if (noise_floor_wait) board.sleepMilliseconds(sleep_ms);
else board.sleep(0);
}
else {
@@ -410,18 +411,17 @@ void loop() {
}
#else
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
if (noise_floor_wait) board.sleepMilliseconds(mesh::power::limitSleepMilliseconds(sleep_secs, noise_floor_delay_ms));
if (noise_floor_wait) board.sleepMilliseconds(sleep_ms);
else board.sleep(0);
#endif
}
#else
if (sleep_secs > 0
if (sleep_ms > 0
&& the_mesh.millisHasNowPassed(
POWERSAVING_FIRST_SLEEP_SECS * 1000UL)) {
if (mesh::powerdiag::enabled()) mesh::powerdiag::decision(0, millis());
if (noise_floor_wait) board.sleepMilliseconds(mesh::power::limitSleepMilliseconds(sleep_secs, noise_floor_delay_ms));
else board.sleep(sleep_secs);
} else if (sleep_secs > 0 && mesh::powerdiag::enabled()) {
board.sleepMilliseconds(sleep_ms);
} else if (sleep_ms > 0 && mesh::powerdiag::enabled()) {
mesh::powerdiag::decision(mesh::powerdiag::mask(mesh::powerdiag::Reason::Startup), millis());
}
#endif
+5
View File
@@ -5,6 +5,11 @@
namespace mesh {
namespace power {
inline uint32_t secondsToSleepMilliseconds(uint32_t seconds) {
const uint64_t millisecs = static_cast<uint64_t>(seconds) * 1000ULL;
return millisecs > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(millisecs);
}
inline uint32_t powerSaveSleepSeconds(uint32_t delay_ms) {
const uint32_t seconds = delay_ms / 1000UL;
#if defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM) || defined(STM32_PLATFORM)
+8
View File
@@ -25,6 +25,14 @@ public:
&& now_uptime_millis >= hard_end_uptime_millis;
}
static uint32_t millisecondsUntil(uint64_t now_uptime_millis,
uint64_t hard_end_uptime_millis) {
if (hard_end_uptime_millis == 0) return UINT32_MAX;
if (now_uptime_millis >= hard_end_uptime_millis) return 0;
const uint64_t remaining = hard_end_uptime_millis - now_uptime_millis;
return remaining > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(remaining);
}
static uint32_t secondsUntil(uint64_t now_uptime_millis,
uint64_t hard_end_uptime_millis) {
if (hard_end_uptime_millis == 0) return UINT32_MAX;
+21 -3
View File
@@ -53,10 +53,10 @@ struct Mesh {
struct Prefs { bool powersaving_enabled=true,system_watchdog_enabled=true; uint32_t reboot_interval=0; } prefs;
unsigned calls=0;
bool noise_floor_wait=false,work_pending=false;
uint32_t noise_floor_delay_ms=50;
uint32_t noise_floor_delay_ms=50,sleep_budget_ms=1000;
void loop() { ++calls; }
Prefs* getNodePrefs() { return &prefs; }
uint32_t getPowerSaveSleepSeconds(unsigned) { return work_pending ? 0 : 1; }
uint32_t getPowerSaveSleepMilliseconds(unsigned) { return work_pending ? 0 : sleep_budget_ms; }
bool getNextNoiseFloorWakeDelay(uint32_t& ms) const {
ms=noise_floor_delay_ms; return noise_floor_wait;
}
@@ -142,8 +142,9 @@ int main() {
// bounded MCU idle in every actual role loop. Due work and other vetoes win.
the_mesh.noise_floor_wait=true;
auto before_noise_sleep=board.sleeps;
const auto before_noise_ms_sleep=board.millisecond_sleeps;
loop();
assert(board.sleeps==before_noise_sleep+1 && board.millisecond_sleeps==1);
assert(board.sleeps==before_noise_sleep+1 && board.millisecond_sleeps==before_noise_ms_sleep+1);
#if ROLE_COMPANION
assert(board.last_sleep_ms==10); // Preserve the shorter Companion cadence.
#else
@@ -161,4 +162,21 @@ int main() {
the_mesh.work_pending=false; usb_watchdog_armed=true;
loop();
assert(board.sleeps==before_noise_sleep+2);
usb_watchdog_armed=false;
#if !ROLE_COMPANION
// The role's earlier queue/bridge/timer deadline wins over calibration.
the_mesh.sleep_budget_ms=17;
loop();
assert(board.last_sleep_ms==17);
the_mesh.noise_floor_wait=false;
for (uint32_t remaining : {1U,50U,999U,1001U,29999U}) {
const auto before=board.sleeps;
the_mesh.sleep_budget_ms=remaining;
loop();
assert(board.sleeps==before+1);
#if !defined(NRF52_PLATFORM)
assert(board.last_sleep_ms==remaining); // No whole-second busy tail.
#endif
}
#endif
}
+1 -1
View File
@@ -56,7 +56,7 @@ struct MyMesh {
bool hasPendingSerialOutput() { return false; }
bool isDualRadioActive() { return dual_radio_active; }
Prefs* getNodePrefs() { return _prefs; }
unsigned getPowerSaveSleepSeconds(unsigned limit) { return limit; }
unsigned getPowerSaveSleepMilliseconds(unsigned limit) { return limit; }
bool millisHasNowPassed(unsigned deadline) { return millis() > deadline; }
} the_mesh;
static ESP32Board board;
+1
View File
@@ -145,6 +145,7 @@ public:
uint32_t limitSleepToMillisTimer(unsigned long, uint32_t) const;
bool hasPendingWork() const;
mesh::powerdiag::Mask pendingWorkReason() const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t) const;
uint32_t getPowerSaveSleepSeconds(uint32_t) const;
void loopBeforeService();
void sleepIfIdle() { @SLEEP_BLOCK@ }
+1
View File
@@ -36,6 +36,7 @@ static int esp_light_sleep_start() { ++wait_calls; clock_us += 500; return sleep
static unsigned pdMS_TO_TICKS(unsigned ms) { return ms; }
static void vTaskDelay(unsigned ms) { assert(ms == 10); ++delay_calls; clock_us += 10000; }
static void attempt() {
const uint32_t idle_slice_ms = 10;
@BLOCK@
}
int main() {
+2
View File
@@ -240,6 +240,7 @@ struct MyMesh {
void suspendUnconfiguredSetupBridges(); void serviceIdleWiFi();
bool startWebConfigImpl(bool force_ap, char* reply, bool automatic_setup);
bool startWebConfig(bool force_ap, char* reply); bool stopWebConfig(char* reply);
uint32_t getPowerSaveSleepMilliseconds(uint32_t) const;
bool hasPendingWork() const; uint32_t getPowerSaveSleepSeconds(uint32_t) const;
mesh::powerdiag::Mask pendingWorkReason() const;
@INLINE_METHODS@
@@ -391,6 +392,7 @@ class ESP32FullPowerLifecycleTest(unittest.TestCase):
"bool MyMesh::startWebConfig(", "bool MyMesh::startWebConfigImpl(",
"bool MyMesh::stopWebConfig(",
"bool MyMesh::hasPendingWork() const", "mesh::powerdiag::Mask MyMesh::pendingWorkReason() const",
"uint32_t MyMesh::getPowerSaveSleepMilliseconds(",
"uint32_t MyMesh::getPowerSaveSleepSeconds("))
defaults_start = source.index(" // bridge defaults")
defaults = source[defaults_start:source.index(" _prefs.bridge_delay", defaults_start)]
+1
View File
@@ -145,6 +145,7 @@ struct Mesh {
return pending ? mesh::powerdiag::mask(mesh::powerdiag::Reason::Mesh) : 0;
}
bool isDualRadioActive() const { return dual; }
bool getNextNoiseFloorWakeDelay(uint32_t&) const { return false; }
mesh::Radio* getProfileRadio() { return &timing_radio; }
} the_mesh;
struct Interfaces { bool pending = false; bool hasPendingIO() const { return pending; } } interface_manager;
+2
View File
@@ -68,6 +68,7 @@ struct MyMesh {
uint32_t limitSleepToScheduledRadioWork(uint32_t secs) const { return secs; }
bool hasPendingWork() const;
mesh::powerdiag::Mask pendingWorkReason() const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t) const;
uint32_t getPowerSaveSleepSeconds(uint32_t) const;
};
@METHODS@
@@ -151,6 +152,7 @@ class LoggingSleepContractTest(unittest.TestCase):
methods = "\n".join(method(source, signature) for signature in (
"bool MyMesh::hasPendingWork() const",
"mesh::powerdiag::Mask MyMesh::pendingWorkReason() const",
"uint32_t MyMesh::getPowerSaveSleepMilliseconds(uint32_t max_ms) const",
"uint32_t MyMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const",
))
cpp = Path(temp) / f"{role}.cpp"
+1 -1
View File
@@ -98,7 +98,7 @@ struct Radio {
static mesh::Radio radio_driver;
'''.replace("@MORSE_HELPER@", morse_helper)
fixture = (ROOT / "test/fixtures/official_web_console/firmware.cpp").read_text()
sleep_getter = " unsigned getPowerSaveSleepSeconds(unsigned limit) { return limit; }"
sleep_getter = " unsigned getPowerSaveSleepMilliseconds(unsigned limit) { return limit; }"
assert fixture.count(sleep_getter) == 1, "Missing sleep-deadline peripheral boundary"
fixture = fixture.replace(sleep_getter, sleep_getter + "\n" +
" bool getNextNoiseFloorWakeDelay(uint32_t&) const { return false; }")
+89 -2
View File
@@ -14,6 +14,7 @@ HARNESS = r'''
#include <cstdint>
#include <initializer_list>
#include <helpers/PowerManagementUtils.h>
#include <helpers/TempRadioLeaseDeadline.h>
#include <helpers/PowerSavingDiagnostics.h>
#define SENSOR_READ_INTERVAL_SECS 60
static uint32_t now_ms = 1000;
@@ -44,12 +45,27 @@ struct Node {
}
uint32_t limitSleepToScheduledRadioWork(uint32_t seconds) const { return seconds; }
uint32_t limitSleepToMillisTimer(unsigned long, uint32_t) const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t) const;
uint32_t getPowerSaveSleepSeconds(uint32_t) const;
};
@METHODS@
int main() {
Node node;
using Lease = mesh::TempRadioLeaseDeadline;
assert(Lease::millisecondsUntil(10, 0) == UINT32_MAX);
assert(Lease::millisecondsUntil(100, 100) == 0);
assert(Lease::millisecondsUntil(101, 100) == 0);
assert(Lease::millisecondsUntil(100, uint64_t(UINT32_MAX) + 101) == UINT32_MAX);
for (uint64_t now : {1000ULL, 0x100000001ULL, UINT64_MAX - 100000ULL}) {
for (uint32_t delay : {1U, 50U, 999U, 1001U, 30000U})
assert(Lease::millisecondsUntil(now, now + delay) == delay);
}
assert(mesh::power::powerSaveSleepSeconds(0) == 0);
assert(mesh::power::secondsToSleepMilliseconds(0) == 0);
assert(mesh::power::secondsToSleepMilliseconds(30) == 30000);
assert(mesh::power::secondsToSleepMilliseconds(4294967) == 4294967000U);
assert(mesh::power::secondsToSleepMilliseconds(4294968) == UINT32_MAX);
assert(mesh::power::secondsToSleepMilliseconds(UINT32_MAX) == UINT32_MAX);
assert(mesh::power::limitSleepMilliseconds(0, 50) == 0);
assert(mesh::power::limitSleepMilliseconds(30, 0) == 0);
assert(mesh::power::limitSleepMilliseconds(30, 50) == 50);
@@ -63,7 +79,9 @@ int main() {
#endif
assert(node.getPowerSaveSleepSeconds(30) == 30);
assert(node.getPowerSaveSleepSeconds(0) == 0);
assert(node.limitSleepToMillisTimer(0, 30) == 30); // Disabled timer sentinel.
assert(node.getPowerSaveSleepMilliseconds(30000) == 30000);
assert(node.getPowerSaveSleepMilliseconds(0) == 0);
assert(node.limitSleepToMillisTimer(0, 30000) == 30000); // Disabled timer sentinel.
assert(node.limitSleepToMillisTimer(1001, 0) == 0);
node.pending = true;
assert(node.getPowerSaveSleepSeconds(30) == 0);
@@ -82,27 +100,33 @@ int main() {
const uint32_t idle_seconds = whole_seconds;
#endif
const uint32_t expected = idle_seconds < 30 ? idle_seconds : 30;
const uint32_t expected_ms = ms < 30000 ? ms : 30000;
node.queue = true; node.queue_ms = ms;
assert(node.getPowerSaveSleepSeconds(30) == expected);
assert(node.getPowerSaveSleepMilliseconds(30000) == expected_ms);
node.queue = false; node.retry = true; node.retry_ms = ms;
assert(node.getPowerSaveSleepSeconds(30) == expected);
assert(node.getPowerSaveSleepMilliseconds(30000) == expected_ms);
node.retry = false;
const uint32_t deadline = now + ms;
assert(deadline != 0); // Zero has a separate intentional sentinel meaning.
assert(node.limitSleepToMillisTimer(deadline, 30) == expected);
assert(node.limitSleepToMillisTimer(deadline, 30000) == expected_ms);
node.next_flood_advert = deadline;
assert(node.getPowerSaveSleepSeconds(30) == expected);
assert(node.getPowerSaveSleepMilliseconds(30000) == expected_ms);
node.next_flood_advert = 0;
}
assert(node.limitSleepToMillisTimer(uint32_t(now - 1), 30) == 0);
node.queue = true; node.queue_ms = 2500;
node.retry = true; node.retry_ms = 1001;
assert(node.getPowerSaveSleepMilliseconds(30000) == 1001);
#if EXPECT_INTERRUPT_IDLE
assert(node.getPowerSaveSleepSeconds(30) == 2); // Interrupt idle is permitted.
#else
assert(node.getPowerSaveSleepSeconds(30) == 1); // Earliest timed owner wins.
#endif
node.dirty_contacts_expiry = uint32_t(now + 999);
assert(node.getPowerSaveSleepMilliseconds(30000) == 999);
#if EXPECT_INTERRUPT_IDLE
assert(node.getPowerSaveSleepSeconds(30) == 1);
#else
@@ -116,6 +140,68 @@ int main() {
class PowerSavingDeadlineTests(unittest.TestCase):
def test_actual_role_loops_preserve_subsecond_sleep_budgets(self):
from cpp_source import body
harness = r'''
#include <cassert>
#include <cstdint>
#include <initializer_list>
#include <helpers/PowerSavingDiagnostics.h>
#include <helpers/PowerManagementUtils.h>
#define POWERSAVING_FIRSTSLEEP_SECS 1
#define POWERSAVING_FIRST_SLEEP_SECS 1
static uint32_t millis() { return 100000; }
struct Mesh {
uint32_t budget = 30000, noise_delay = 50;
bool noise = false;
uint32_t getPowerSaveSleepMilliseconds(uint32_t limit) const {
assert(limit == 30000); return budget;
}
bool getNextNoiseFloorWakeDelay(uint32_t& out) const {
out = noise_delay; return noise;
}
bool millisHasNowPassed(uint32_t) const { return true; }
} the_mesh;
struct Board {
unsigned calls = 0;
uint32_t last_ms = 0;
void sleep(uint32_t seconds) { ++calls; last_ms = seconds * 1000; }
void sleepMilliseconds(uint32_t ms) { ++calls; last_ms = ms; }
} board;
void idle(bool can_power_save) { @GATE@ }
int main() {
for (uint32_t budget : {0U, 1U, 50U, 999U, 1001U, 29999U, 30000U}) {
the_mesh.budget = budget;
for (bool noise : {false, true}) {
the_mesh.noise = noise;
const auto before = board.calls;
idle(true);
assert(board.calls == before + (budget != 0));
if (budget) assert(board.last_ms == (noise && budget > 50 ? 50 : budget));
const auto after = board.calls;
idle(false); assert(board.calls == after);
}
}
the_mesh.noise_delay = 0;
const auto before = board.calls;
idle(true); assert(board.calls == before);
}
'''
with tempfile.TemporaryDirectory(prefix='meshcore-role-ms-idle-') as directory:
cpp, exe = Path(directory) / 'test.cpp', Path(directory) / 'test'
for role in ('simple_repeater', 'simple_room_server', 'simple_sensor'):
with self.subTest(role=role):
source = (ROOT / 'examples' / role / 'main.cpp').read_text()
gate = body(source, ' if (can_power_save) {')
cpp.write_text(harness.replace('@GATE@', gate), encoding='ascii')
built = subprocess.run([
os.environ.get('CXX', 'g++'), '-std=c++17', '-Wall', '-Wextra', '-Werror',
'-I', str(ROOT / 'src'), str(cpp), '-o', str(exe),
], capture_output=True, text=True)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
ran = subprocess.run([str(exe)], capture_output=True, text=True)
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
def test_real_board_millisecond_idle_preserves_port_wake_contract(self):
source = (ROOT / 'src/MeshCore.h').read_text(encoding='ascii')
implementation = method(source, 'virtual void sleepMilliseconds(uint32_t millisecs)')
@@ -172,6 +258,7 @@ int main() {
source = (ROOT / filename).read_text()
methods = '\n'.join(method(source, signature) for signature in (
f'uint32_t {name}::limitSleepToMillisTimer(',
f'uint32_t {name}::getPowerSaveSleepMilliseconds(',
f'uint32_t {name}::getPowerSaveSleepSeconds('))
methods = methods.replace(name + '::', 'Node::')
cpp, exe = work / (role + '.cpp'), work / role
+15 -4
View File
@@ -157,6 +157,7 @@ int main() {
methods = "\n".join(extract_braced(source, signature) for signature in (
"bool MyMesh::hasPendingWork() const",
"mesh::powerdiag::Mask MyMesh::pendingWorkReason() const",
"uint32_t MyMesh::getPowerSaveSleepMilliseconds(uint32_t max_ms) const",
"uint32_t MyMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const",
))
code = r'''
@@ -210,6 +211,7 @@ struct MyMesh {
uint32_t limitSleepToScheduledRadioWork(uint32_t value) const { return value; }
bool hasPendingWork() const;
mesh::powerdiag::Mask pendingWorkReason() const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t max_ms) const;
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs) const;
};
@METHODS@
@@ -240,8 +242,9 @@ int main() {
assert(node.pendingWorkReason() == mask(Reason::BridgeWake));
node.uart.wake_ms = 500;
assert(!node.hasPendingWork());
assert(node.getPowerSaveSleepSeconds(30) == 0); // Whole-second bridge rendezvous floor.
if (diagnostics) assert(snapshot(millis()).current == mask(Reason::BridgeWake));
reset(millis());
assert(node.getPowerSaveSleepMilliseconds(30000) == 500);
if (diagnostics) assert(snapshot(millis()).current == 0);
node.uart.wake_ms = 60000;
check(node.radio_driver.watchdog, Reason::RadioWatchdog);
check(node.radio_driver.calibration, Reason::Calibration);
@@ -253,10 +256,18 @@ int main() {
check(node.queue, Reason::Queue); check(node.retry, Reason::Retry);
check(node.scheduled, Reason::Scheduled);
node.queue_delay_set = true; node.queue_delay = 500;
assert(!node.hasPendingWork() && node.getPowerSaveSleepSeconds(30) == 0);
reset(millis());
assert(!node.hasPendingWork() && node.getPowerSaveSleepMilliseconds(30000) == 500);
if (diagnostics) assert(snapshot(millis()).current == 0);
node.queue_delay = 0;
assert(node.getPowerSaveSleepMilliseconds(30000) == 0);
if (diagnostics) assert(snapshot(millis()).current == mask(Reason::Queue));
node.queue_delay_set = false; node.retry_delay_set = true; node.retry_delay = 500;
assert(node.getPowerSaveSleepSeconds(30) == 0);
reset(millis());
assert(node.getPowerSaveSleepMilliseconds(30000) == 500);
if (diagnostics) assert(snapshot(millis()).current == 0);
node.retry_delay = 0;
assert(node.getPowerSaveSleepMilliseconds(30000) == 0);
if (diagnostics) assert(snapshot(millis()).current == mask(Reason::Retry));
node.retry_delay_set = false; node.dirty_contacts_expiry = millis();
assert(node.pendingWorkReason() == mask(Reason::Scheduled));
+3
View File
@@ -166,6 +166,7 @@ struct MyMesh : Callbacks {
uint32_t limitSleepToScheduledRadioWork(uint32_t secs) const { return secs; }
bool hasPendingWork() const;
mesh::powerdiag::Mask pendingWorkReason() const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t) const;
uint32_t getPowerSaveSleepSeconds(uint32_t) const;
@OWNER@
~MyMesh() override { assert(setRs232BridgeState(false)); }
@@ -225,6 +226,7 @@ int main() {
const uint32_t expected = remainder / 1000U;
#endif
assert(node.getPowerSaveSleepSeconds(30) == expected);
assert(node.getPowerSaveSleepMilliseconds(30000) == remainder);
}
node.bridge->wake_delay = 0; assert(node.hasPendingWork());
#ifdef WITH_MQTT_BRIDGE
@@ -309,6 +311,7 @@ class RS232HeartbeatOwnerTests(unittest.TestCase):
sleep = "\n".join(extract_braced(implementation, signature) for signature in (
"bool MyMesh::hasPendingWork() const",
"mesh::powerdiag::Mask MyMesh::pendingWorkReason() const",
"uint32_t MyMesh::getPowerSaveSleepMilliseconds(uint32_t max_ms) const",
"uint32_t MyMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const",
))
source = HARNESS.replace("@OWNER@", owner).replace("@SLEEP@", sleep)
+5 -2
View File
@@ -67,6 +67,7 @@ public:
uint32_t limitSleepToMillisTimer(unsigned long timestamp,uint32_t sleep_secs)const;
bool hasPendingWork()const;
mesh::powerdiag::Mask pendingWorkReason()const;
uint32_t getPowerSaveSleepMilliseconds(uint32_t max_ms)const;
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs)const;
#if MESH_ENABLE_SENSOR_TRACKER
bool trackerNeedsRadio()const;
@@ -159,7 +160,7 @@ static void trackerWakeCallback(TimerHandle_t){}
@ARM@
struct Mesh {bool tracker=true;uint32_t sleep_secs=30;
bool isTrackerModeEnabled()const{return tracker;}
uint32_t getPowerSaveSleepSeconds(uint32_t max)const{assert(max==30);return sleep_secs;}
uint32_t getPowerSaveSleepMilliseconds(uint32_t max)const{assert(max==30000);return sleep_secs*1000;}
bool getNextNoiseFloorWakeDelay(uint32_t&)const{return false;}
bool millisHasNowPassed(uint32_t deadline)const{return (int32_t)(millis()-deadline)>=0;}
} the_mesh;
@@ -187,7 +188,7 @@ int main(){
the_mesh.sleep_secs=0;sleepGate(true);assert(board.sleeps==1);
#else
now_ms=119999;sleepGate(true);assert(board.sleeps==0);
now_ms=120000;sleepGate(true);assert(board.sleeps==1&&board.last==30);
now_ms=120000;sleepGate(true);assert(board.sleeps==1&&board.last==30000);
the_mesh.sleep_secs=0;sleepGate(true);assert(board.sleeps==1);
#endif
}
@@ -286,6 +287,7 @@ class SensorTrackerSleepTest(unittest.TestCase):
methods = "\n".join(extract_braced(sensor, name) for name in (
"bool SensorMesh::isMillisTimerDue(", "uint32_t SensorMesh::limitSleepToMillisTimer(",
"bool SensorMesh::hasPendingWork(", "mesh::powerdiag::Mask SensorMesh::pendingWorkReason(",
"uint32_t SensorMesh::getPowerSaveSleepMilliseconds(",
"uint32_t SensorMesh::getPowerSaveSleepSeconds("))
methods += "\n#if MESH_ENABLE_SENSOR_TRACKER\n" + extract_braced(
tracker, "bool SensorMesh::trackerNeedsRadio(") + "\n#endif\n"
@@ -418,6 +420,7 @@ int main(){
methods = "\n".join(body(sensor, signature) for signature in (
"bool SensorMesh::isMillisTimerDue(", "uint32_t SensorMesh::limitSleepToMillisTimer(",
"bool SensorMesh::hasPendingWork(", "mesh::powerdiag::Mask SensorMesh::pendingWorkReason(",
"uint32_t SensorMesh::getPowerSaveSleepMilliseconds(",
"uint32_t SensorMesh::getPowerSaveSleepSeconds("))
methods += "\n" + "\n".join(body(tracker, signature) for signature in (
"bool SensorMesh::trackerNeedsRadio(", "void SensorMesh::wakeTrackerRadio("))