diff --git a/src/ESPNetwork.cpp b/src/ESPNetwork.cpp index d25abde..afc39ed 100644 --- a/src/ESPNetwork.cpp +++ b/src/ESPNetwork.cpp @@ -533,9 +533,9 @@ uint32_t ESPNetwork::getChipId() { return chipId; } bool ESPNetwork::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel) { - // The new AP must be at least ROAM_MIN_GAIN_DB stronger. const bool connected = this->connected(); - int32_t strength = connected ? WiFi.RSSI() + ROAM_MIN_GAIN_DB : -127; + const int32_t currentRssi = connected ? WiFi.RSSI() : 0; + int32_t strength = -127; int32_t chan = -1; memset(bssid, 0x00, 6); esp_task_wdt_delete(NULL); @@ -552,7 +552,7 @@ bool ESPNetwork::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *chann ESP_LOGI(TAG, "AP %02X:%02X:%02X:%02X:%02X:%02X ch %ld %ld dBm%s", b[0], b[1], b[2], b[3], b[4], b[5], (long)WiFi.channel(i), (long)WiFi.RSSI(i), (cur && memcmp(b, cur, 6) == 0) ? " (connected)" : ""); } - if(WiFi.RSSI(i) > strength) { + if(worthRoaming(connected, currentRssi, WiFi.RSSI(i), ROAM_MIN_GAIN_DB) && WiFi.RSSI(i) > strength) { strength = WiFi.RSSI(i); memcpy(bssid, WiFi.BSSID(i), 6); *channel = chan = WiFi.channel(i); @@ -671,22 +671,17 @@ void ESPNetwork::emitHeap(uint8_t num) { uint32_t freeHeap = ESP.getFreeHeap(); uint32_t maxHeap = ESP.getMaxAllocHeap(); uint32_t minHeap = ESP.getMinFreeHeap(); - const bool bBroadcast = num == 255 && millis() - _lastHeapEmit >= HEAP_EMIT_MS; - const bool bRoomEmit = sockEmit.activeClients(0) > 0; - if(num != 255 || bBroadcast || bRoomEmit) { - JsonSockEvent *json = sockEmit.beginEmit("memStatus"); - json->beginObject(); - json->addElem("max", maxHeap); - json->addElem("free", freeHeap); - json->addElem("min", minHeap); - json->addElem("total", ESP.getHeapSize()); - json->addElem("uptime", (uint64_t)millis()); - json->endObject(); - if(bBroadcast) { - sockEmit.endEmit(num); - _lastHeapEmit = millis(); - } - else if(num != 255) sockEmit.endEmit(num); - else sockEmit.endEmitRoom(0); - } + const heap_emit_t target = heapEmitTarget(num, millis() - _lastHeapEmit, HEAP_EMIT_MS, sockEmit.activeClients(0) > 0); + if(target == heap_emit_t::none) return; + JsonSockEvent *json = sockEmit.beginEmit("memStatus"); + json->beginObject(); + json->addElem("max", maxHeap); + json->addElem("free", freeHeap); + json->addElem("min", minHeap); + json->addElem("total", ESP.getHeapSize()); + json->addElem("uptime", (uint64_t)millis()); + json->endObject(); + if(target == heap_emit_t::room) sockEmit.endEmitRoom(0); + else sockEmit.endEmit(num); + if(target == heap_emit_t::broadcast) _lastHeapEmit = millis(); } diff --git a/src/ESPNetwork.h b/src/ESPNetwork.h index e90d0ed..32df40d 100644 --- a/src/ESPNetwork.h +++ b/src/ESPNetwork.h @@ -16,7 +16,7 @@ // How often memStatus is broadcast. It is also the one event that is always // sent, so it must stay well under Home Assistant's 60s receive timeout. #define HEAP_EMIT_MS 30000 -// A roaming scan only moves to another AP this much stronger. +// A roaming scan only moves to another AP at least this much stronger. #define ROAM_MIN_GAIN_DB 6 class ESPNetwork { protected: diff --git a/src/Utils.cpp b/src/Utils.cpp index becb358..0d1691b 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -144,3 +144,11 @@ void RssiBroadcast::sent(bool broadcast, int rssi, int channel, uint32_t now) { this->channel = channel; this->at = now; } +bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain) { + return !connected || apRssi >= currentRssi + minGain; +} +heap_emit_t heapEmitTarget(uint8_t num, uint32_t sinceLastMs, uint32_t intervalMs, bool roomHasClients) { + if(num != 255) return heap_emit_t::client; + if(sinceLastMs >= intervalMs) return heap_emit_t::broadcast; + return roomHasClients ? heap_emit_t::room : heap_emit_t::none; +} diff --git a/src/Utils.h b/src/Utils.h index ebe5597..b721c5f 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -69,6 +69,15 @@ struct RssiBroadcast { bool due(int rssi, int channel, uint32_t now, int minDelta, uint32_t minMs) const; void sent(bool broadcast, int rssi, int channel, uint32_t now); }; +// Whether an AP a scan found for our SSID is worth connecting to. While +// connected it has to be at least minGain dBm stronger than the current AP so +// a small swing doesn't make the device hop between APs. +bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain); +// Who gets a memStatus event. +enum class heap_emit_t : uint8_t { none, client, broadcast, room }; +// num is the socket client asked for, 255 for everyone. A broadcast goes out +// every intervalMs; in between only the clients in room 0 get one. +heap_emit_t heapEmitTarget(uint8_t num, uint32_t sinceLastMs, uint32_t intervalMs, bool roomHasClients); class Timestamp { char _timeBuffer[128]; diff --git a/test/test_utils/test_utils.cpp b/test/test_utils/test_utils.cpp index 7d483bd..80e76cd 100644 --- a/test/test_utils/test_utils.cpp +++ b/test/test_utils/test_utils.cpp @@ -240,6 +240,38 @@ static void test_rssiBroadcast_hold_off_across_millis_wrap() { TEST_ASSERT_TRUE(b.due(-70, 6, 0x00002000u, 3, 10000)); // 12288ms later } +/********************************************************************* + * worthRoaming + ********************************************************************/ +static void test_worthRoaming_needs_the_full_gain() { + TEST_ASSERT_FALSE(worthRoaming(true, -70, -65, 6)); + TEST_ASSERT_TRUE(worthRoaming(true, -70, -64, 6)); + TEST_ASSERT_TRUE(worthRoaming(true, -70, -50, 6)); +} +static void test_worthRoaming_never_to_a_weaker_ap() { + // The scan also lists the AP we are on. + TEST_ASSERT_FALSE(worthRoaming(true, -70, -70, 6)); + TEST_ASSERT_FALSE(worthRoaming(true, -70, -80, 6)); +} +static void test_worthRoaming_any_ap_while_disconnected() { + TEST_ASSERT_TRUE(worthRoaming(false, 0, -90, 6)); +} + +/********************************************************************* + * heapEmitTarget + ********************************************************************/ +static void test_heapEmitTarget_one_client_always_gets_it() { + TEST_ASSERT_TRUE(heap_emit_t::client == heapEmitTarget(3, 0, 30000, false)); + TEST_ASSERT_TRUE(heap_emit_t::client == heapEmitTarget(3, 60000, 30000, true)); +} +static void test_heapEmitTarget_broadcasts_once_per_interval() { + TEST_ASSERT_TRUE(heap_emit_t::broadcast == heapEmitTarget(255, 30000, 30000, false)); + TEST_ASSERT_TRUE(heap_emit_t::broadcast == heapEmitTarget(255, 30000, 30000, true)); + TEST_ASSERT_TRUE(heap_emit_t::none == heapEmitTarget(255, 29999, 30000, false)); +} +static void test_heapEmitTarget_room_in_between() { + TEST_ASSERT_TRUE(heap_emit_t::room == heapEmitTarget(255, 1000, 30000, true)); +} int main(int argc, char **argv) { (void)argc; (void)argv; @@ -276,5 +308,11 @@ int main(int argc, char **argv) { RUN_TEST(test_rssiBroadcast_channel_change_is_due); RUN_TEST(test_rssiBroadcast_client_emit_on_disconnect_keeps_baseline); RUN_TEST(test_rssiBroadcast_hold_off_across_millis_wrap); + RUN_TEST(test_worthRoaming_needs_the_full_gain); + RUN_TEST(test_worthRoaming_never_to_a_weaker_ap); + RUN_TEST(test_worthRoaming_any_ap_while_disconnected); + RUN_TEST(test_heapEmitTarget_one_client_always_gets_it); + RUN_TEST(test_heapEmitTarget_broadcasts_once_per_interval); + RUN_TEST(test_heapEmitTarget_room_in_between); return UNITY_END(); }