From 25d548ef070e0a144f9ae1084921c6d369659257 Mon Sep 17 00:00:00 2001 From: cjkas Date: Wed, 21 Oct 2026 15:27:21 +0200 Subject: [PATCH] fix: send socket events only from loop() Every socket event is formatted into one static buffer by one shared formatter. setConnected() runs on the WiFi/Ethernet event task and sent memStatus, wifiStrength and ethernet, then ran sockEmit.loop(), while loop() could be building its own event. _safecat() checks the room and then writes at _len, so another task moving _len in between could write past the end of g_response. setConnected() now sends nothing. loop() broadcasts the status every STATUS_EMIT_MS (30 s): memStatus, then ethernet, or wifiStrength when it moved 3 dBm or changed channel. A client that connects still gets all of it at once from initClients(), so needsBroadcast and the 1.5 s tick go, and so does the 1.5 s memStatus for the config panel. SocketEmitter records loop()'s task in begin(). Off it, beginEmit() logs and hands out a formatter that writes nothing, and endEmit(), endEmitRoom() and loop() do nothing. Co-Authored-By: Claude Opus 5.5 --- src/ESPNetwork.cpp | 48 +++++------------ src/ESPNetwork.h | 12 ++--- src/JsonFormatter.cpp | 11 ++++ src/JsonFormatter.h | 3 ++ src/Utils.cpp | 19 +++---- src/Utils.h | 19 +++---- src/entryPoints/Web.cpp | 1 - src/outputs/SocketEmitter.cpp | 18 +++++++ src/outputs/SocketEmitter.h | 5 ++ test/test_json/test_json.cpp | 35 ++++++++++++ test/test_utils/test_utils.cpp | 99 ++++++++++++++++------------------ 11 files changed, 153 insertions(+), 117 deletions(-) diff --git a/src/ESPNetwork.cpp b/src/ESPNetwork.cpp index d9df416..054c42f 100644 --- a/src/ESPNetwork.cpp +++ b/src/ESPNetwork.cpp @@ -22,8 +22,6 @@ extern ESPNetwork net; extern SomfyShadeController somfy; extern LoopStats loopStats; -static unsigned long _lastHeapEmit = 0; - static bool _apScanning = false; int connectRetries = 0; void ESPNetwork::end() { @@ -144,13 +142,9 @@ void ESPNetwork::loop() { } } } - if(millis() - this->lastEmit > 1500) { - // Post our connection status if needed. + if(millis() - this->lastEmit >= STATUS_EMIT_MS) { this->lastEmit = millis(); - if(this->connected()) { - this->emitSockets(); - this->lastEmit = millis(); - } + if(this->connected()) this->emitSockets(); resetTaskWdt(); // Make sure we do not reboot here. } @@ -176,15 +170,13 @@ bool ESPNetwork::changeAP(const uint8_t *bssid, const int32_t channel) { this->connectStart = millis(); return false; } +// The status broadcast, every STATUS_EMIT_MS from loop(). A wired link goes +// out every time, the WiFi signal only when it really changed. void ESPNetwork::emitSockets() { this->emitHeap(); - if(this->needsBroadcast || - (this->connType == conn_types_t::wifi && - this->rssiBroadcast.due(WiFi.RSSI(), WiFi.channel(), millis(), RSSI_EMIT_MIN_DBM, RSSI_EMIT_MIN_MS))) { + if(this->connType != conn_types_t::wifi || + this->rssiBroadcast.due(WiFi.RSSI(), WiFi.channel(), RSSI_EMIT_MIN_DBM)) this->emitSockets(255); - sockEmit.loop(); - this->needsBroadcast = false; - } } void ESPNetwork::emitSockets(uint8_t num) { #if HAS_ETHERNET @@ -208,7 +200,7 @@ void ESPNetwork::emitSockets(uint8_t num) { json->addElem("channel", (int32_t)this->channel); json->endObject(); sockEmit.endEmit(num); - this->rssiBroadcast.sent(num == 255, WiFi.RSSI(), WiFi.channel(), millis()); + this->rssiBroadcast.sent(num == 255, WiFi.RSSI(), WiFi.channel()); } else { JsonSockEvent *json = sockEmit.beginEmit("wifiStrength"); @@ -226,12 +218,15 @@ void ESPNetwork::emitSockets(uint8_t num) { json->addElem("fullduplex", false); json->endObject(); sockEmit.endEmit(num); - this->rssiBroadcast.sent(num == 255, -100, -1, millis()); + this->rssiBroadcast.sent(num == 255, -100, -1); } } // A broadcast comes from emitSockets(), which has already sent memStatus. if(num != 255) this->emitHeap(num); } +// Runs on the WiFi/Ethernet event task, so it sends nothing on the sockets. +// loop() broadcasts the new status and a client that reconnects gets it from +// SocketEmitter::initClients(). void ESPNetwork::setConnected(conn_types_t connType) { resetTaskWdt(); this->connType = connType; @@ -287,15 +282,6 @@ void ESPNetwork::setConnected(conn_types_t connType) { settings.IP.dns1 = ETH.dnsIP(0); settings.IP.dns2 = ETH.dnsIP(1); } - resetTaskWdt(); - JsonSockEvent *json = sockEmit.beginEmit("ethernet"); - json->beginObject(); - json->addElem("connected", this->connected()); - json->addElem("speed", (uint32_t)ETH.linkSpeed()); - json->addElem("fullduplex", ETH.fullDuplex()); - json->endObject(); - sockEmit.endEmit(); - resetTaskWdt(); } #endif } @@ -347,9 +333,7 @@ void ESPNetwork::setConnected(conn_types_t connType) { } else if(SSDP.isStarted) SSDP.end(); resetTaskWdt(); - this->emitSockets(); settings.printAvailHeap(); - this->needsBroadcast = true; } #if HAS_ETHERNET bool ESPNetwork::connectWired() { @@ -693,8 +677,6 @@ void ESPNetwork::emitHeap(uint8_t num) { uint32_t freeHeap = ESP.getFreeHeap(); uint32_t maxHeap = ESP.getMaxAllocHeap(); uint32_t minHeap = ESP.getMinFreeHeap(); - 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); @@ -707,10 +689,6 @@ void ESPNetwork::emitHeap(uint8_t num) { json->addElem("loopRate", loopStats.rate(millis())); json->addElem("loopMaxUs", loopStats.maxUs()); json->endObject(); - if(target == heap_emit_t::room) sockEmit.endEmitRoom(0); - else sockEmit.endEmit(num); - if(target == heap_emit_t::broadcast) { - _lastHeapEmit = millis(); - loopStats.publish(_lastHeapEmit); - } + sockEmit.endEmit(num); + if(num == 255) loopStats.publish(millis()); } diff --git a/src/ESPNetwork.h b/src/ESPNetwork.h index 32df40d..d6ad114 100644 --- a/src/ESPNetwork.h +++ b/src/ESPNetwork.h @@ -10,12 +10,13 @@ #define CONNECT_TIMEOUT 20000 #define SSID_SCAN_INTERVAL 600000 // A weak signal moves a dBm or two all the time, so only broadcast real -// changes and not more often than this. +// changes. #define RSSI_EMIT_MIN_DBM 3 -#define RSSI_EMIT_MIN_MS 10000 -// 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 +// How often loop() broadcasts the status: memStatus, then wifiStrength or +// ethernet. memStatus is the one event that is always sent, so this must stay +// well under Home Assistant's 60s receive timeout. A client that connects gets +// all of it straight away from SocketEmitter::initClients(). +#define STATUS_EMIT_MS 30000 // A roaming scan only moves to another AP at least this much stronger. #define ROAM_MIN_GAIN_DB 6 class ESPNetwork { @@ -31,7 +32,6 @@ class ESPNetwork { bool wifiFallback = false; bool softAPOpened = false; bool openingSoftAP = false; - bool needsBroadcast = true; conn_types_t connType = conn_types_t::unset; conn_types_t connTarget = conn_types_t::unset; bool connected(); diff --git a/src/JsonFormatter.cpp b/src/JsonFormatter.cpp index 87b7a20..a849f0c 100644 --- a/src/JsonFormatter.cpp +++ b/src/JsonFormatter.cpp @@ -16,6 +16,17 @@ void BufferedJsonFormatter::setBuffer(char *b, size_t sz) { size_t BufferedJsonFormatter::length() const { return this->_len; } +void JsonFormatter::discard() { + // With no room every _safecat() takes the overflow path, and as it has + // already overflowed that doesn't log either. + this->buff = nullptr; + this->buffSize = 0; + this->_len = 0; + this->_nocomma = true; + this->_overflow = true; + this->_objects = 0; + this->_arrays = 0; +} void JsonFormatter::beginObject(const char *name) { if(name && strlen(name) > 0) this->appendElem(name); else if(!this->_nocomma) this->_safecat(","); diff --git a/src/JsonFormatter.h b/src/JsonFormatter.h index 3f93025..639b465 100644 --- a/src/JsonFormatter.h +++ b/src/JsonFormatter.h @@ -24,6 +24,9 @@ class JsonFormatter { void _appendNumber(const char *name); public: bool overflowed() const { return _overflow; } + // Drops whatever is written until a buffer is set again. Nothing is + // written anywhere, so tasks can share one discarding formatter. + void discard(); // Writes c as it must appear inside a JSON string (out needs 7 bytes) and // returns its length. static size_t escapeChar(char c, char *out); diff --git a/src/Utils.cpp b/src/Utils.cpp index 3b55d0b..933ec3a 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -130,19 +130,21 @@ int Timestamp::tzOffset() { time(&now); return Timestamp::calcTZOffset(&now); } -bool rssiNeedsEmit(int rssi, int lastRssi, int channel, int lastChannel, uint32_t sinceLastMs, int minDelta, uint32_t minMs) { +bool rssiNeedsEmit(int rssi, int lastRssi, int channel, int lastChannel, int minDelta) { if(channel != lastChannel) return true; int delta = rssi > lastRssi ? rssi - lastRssi : lastRssi - rssi; - return delta >= minDelta && sinceLastMs >= minMs; + return delta >= minDelta; } -bool RssiBroadcast::due(int rssi, int channel, uint32_t now, int minDelta, uint32_t minMs) const { - return rssiNeedsEmit(rssi, this->rssi, channel, this->channel, now - this->at, minDelta, minMs); +bool RssiBroadcast::due(int rssi, int channel, int minDelta) const { + return rssiNeedsEmit(rssi, this->rssi, channel, this->channel, minDelta); } -void RssiBroadcast::sent(bool broadcast, int rssi, int channel, uint32_t now) { +void RssiBroadcast::sent(bool broadcast, int rssi, int channel) { if(!broadcast) return; this->rssi = rssi; this->channel = channel; - this->at = now; +} +bool emitAllowed(const void *current, const void *owner) { + return owner == nullptr || current == owner; } bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain) { return !connected || apRssi >= currentRssi + minGain; @@ -179,11 +181,6 @@ const char *wifiAuthModeName(uint8_t authMode) { default: return "UNKNOWN"; } } -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; -} void LoopStats::record(uint32_t passUs) { _passes++; if(passUs > _maxUs) _maxUs = passUs; diff --git a/src/Utils.h b/src/Utils.h index 4032678..0ad22e7 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -58,30 +58,27 @@ struct rebootDelay_t { return def; } // Whether a wifiStrength broadcast is worth sending: always on a channel change, -// otherwise only for an RSSI change of at least minDelta dBm and not more often -// than every minMs. -bool rssiNeedsEmit(int rssi, int lastRssi, int channel, int lastChannel, uint32_t sinceLastMs, int minDelta, uint32_t minMs); +// otherwise only for an RSSI change of at least minDelta dBm. +bool rssiNeedsEmit(int rssi, int lastRssi, int channel, int lastChannel, int minDelta); // What the last wifiStrength broadcast told every client. Only broadcasts // move it: an emit to one new client tells the others nothing. struct RssiBroadcast { int rssi = 0; int channel = 0; - uint32_t at = 0; // millis() of the last broadcast. - 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); + bool due(int rssi, int channel, int minDelta) const; + void sent(bool broadcast, int rssi, int channel); }; // 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); +// Whether the calling task may send socket events. They are all formatted +// into one shared buffer, so only loop()'s task may. owner is that task, or +// null before SocketEmitter::begin() has recorded it (setup() runs there too). +bool emitAllowed(const void *current, const void *owner); // A short name for a wifi_auth_mode_t value, for the logs. The values up to // WAPI are the same in ESP-IDF 4.4 and 5.x. const char *wifiAuthModeName(uint8_t authMode); -// 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); // Whether a firmware the bootloader is holding for verification after an // update has proven itself: it has run for minUptimeMs and the network is // up. Until then a reset rolls back to the previous firmware. diff --git a/src/entryPoints/Web.cpp b/src/entryPoints/Web.cpp index 0659def..7026f2b 100644 --- a/src/entryPoints/Web.cpp +++ b/src/entryPoints/Web.cpp @@ -327,7 +327,6 @@ void Web::handleDiscovery(AsyncWebServerRequest *request) { if(request->method() == HTTP_POST || request->method() == HTTP_GET) { ESP_LOGD(TAG, "Async Discovery Requested"); sendSnapshot(request, snapshot_doc_t::discovery, 0, discoveryHeader); - net.needsBroadcast = true; } else request->send(500, _encoding_text, "Invalid http method"); diff --git a/src/outputs/SocketEmitter.cpp b/src/outputs/SocketEmitter.cpp index 85bd85a..f84a133 100644 --- a/src/outputs/SocketEmitter.cpp +++ b/src/outputs/SocketEmitter.cpp @@ -8,6 +8,7 @@ #include "ESPNetwork.h" #include "GitOTA.h" #include "Timing.h" +#include "Utils.h" static const char *TAG = "Sockets"; @@ -92,20 +93,36 @@ void SocketEmitter::startup() { } void SocketEmitter::begin() { + // Called from setup(), which runs on loop()'s task. + this->_owner = xTaskGetCurrentTaskHandle(); ws.onEvent(SocketEmitter::wsEvent); wsServer.addHandler(&ws); wsServer.begin(); ESP_LOGI(TAG, "Socket Server Started..."); } +bool SocketEmitter::onLoopTask() const { return emitAllowed(xTaskGetCurrentTaskHandle(), this->_owner); } void SocketEmitter::loop() { + if(!this->onLoopTask()) { + ESP_LOGE(TAG, "Socket loop off loop() from %s, skipped", pcTaskGetName(nullptr)); + return; + } ws.cleanupClients(); this->initClients(); } JsonSockEvent *SocketEmitter::beginEmit(const char *evt) { + // Another task would overwrite the event loop() is building in g_response, + // and could write past its end. + if(!this->onLoopTask()) { + ESP_LOGE(TAG, "Socket emit %s off loop() from %s, dropped", evt, pcTaskGetName(nullptr)); + this->_discard.discard(); + return &this->_discard; + } this->json.beginEvent(&ws, evt, g_response, sizeof(g_response)); return &this->json; } void SocketEmitter::endEmit(uint8_t num) { + // beginEmit() has logged it. + if(!this->onLoopTask()) return; if(num == 255) { this->json.endEvent(0); } else { @@ -115,6 +132,7 @@ void SocketEmitter::endEmit(uint8_t num) { resetTaskWdt(); } void SocketEmitter::endEmitRoom(uint8_t room) { + if(!this->onLoopTask()) return; if(room < SOCK_MAX_ROOMS) { room_t *r = &this->rooms[room]; for(uint8_t i = 0; i < sizeof(r->clients); i++) { diff --git a/src/outputs/SocketEmitter.h b/src/outputs/SocketEmitter.h index 8b349d1..6b0dc67 100644 --- a/src/outputs/SocketEmitter.h +++ b/src/outputs/SocketEmitter.h @@ -22,6 +22,11 @@ class SocketEmitter { protected: uint8_t newclients = 0; uint8_t newClients[5] = {255,255,255,255,255}; + // loop()'s task, recorded by begin(). Only it may send; see emitAllowed(). + TaskHandle_t _owner = nullptr; + // What beginEmit() hands any other task, so the event goes nowhere. + JsonSockEvent _discard; + bool onLoopTask() const; void delayInit(uint8_t num); public: JsonSockEvent json; diff --git a/test/test_json/test_json.cpp b/test/test_json/test_json.cpp index 1dfa190..4bc9109 100644 --- a/test/test_json/test_json.cpp +++ b/test/test_json/test_json.cpp @@ -195,6 +195,39 @@ static void test_setBuffer_resets_length() { TEST_ASSERT_EQUAL_STRING("", storage); } +/********************************************************************* + * discard + ********************************************************************/ +static void test_discard_writes_nothing() { + // What a socket event built off loop() goes into. The buffer the formatter + // had before must not see a byte of it. + fmt.beginObject(); + fmt.addElem("kept", true); + char before[STORAGE_SIZE]; + memcpy(before, storage, sizeof(before)); + fmt.discard(); + fmt.beginObject(); + fmt.addElem("ssid", "Home \"WiFi\""); + fmt.addElem("strength", (int32_t)-70); + fmt.beginArray("pulses"); + fmt.addElem((uint32_t)2416); + fmt.endArray(); + fmt.addElem("big", (uint64_t)18446744073709551615ULL); + fmt.endObject(); + TEST_ASSERT_EQUAL_MEMORY(before, storage, sizeof(before)); + TEST_ASSERT_EQUAL_UINT32(0, fmt.length()); + TEST_ASSERT_TRUE(fmt.overflowed()); +} +static void test_setBuffer_after_discard_writes_again() { + fmt.discard(); + fmt.addElem("dropped"); + useBuffer(BUFF_SIZE); + fmt.beginObject(); + fmt.endObject(); + TEST_ASSERT_FALSE(fmt.overflowed()); + TEST_ASSERT_EQUAL_STRING("{}", storage); +} + int main(int argc, char **argv) { (void)argc; (void)argv; @@ -216,5 +249,7 @@ int main(int argc, char **argv) { RUN_TEST(test_length_matches_what_was_written); RUN_TEST(test_refused_append_leaves_length_alone); RUN_TEST(test_setBuffer_resets_length); + RUN_TEST(test_discard_writes_nothing); + RUN_TEST(test_setBuffer_after_discard_writes_again); return UNITY_END(); } diff --git a/test/test_utils/test_utils.cpp b/test/test_utils/test_utils.cpp index f7fcdee..6cbf252 100644 --- a/test/test_utils/test_utils.cpp +++ b/test/test_utils/test_utils.cpp @@ -181,63 +181,73 @@ static void test_parseUTCTime_ignores_local_zone() { * rssiNeedsEmit ********************************************************************/ static void test_rssiNeedsEmit_channel_change_always_emits() { - TEST_ASSERT_TRUE(rssiNeedsEmit(-70, -70, 6, 11, 0, 3, 10000)); + TEST_ASSERT_TRUE(rssiNeedsEmit(-70, -70, 6, 11, 3)); } static void test_rssiNeedsEmit_small_change_is_ignored() { - TEST_ASSERT_FALSE(rssiNeedsEmit(-72, -70, 6, 6, 60000, 3, 10000)); + TEST_ASSERT_FALSE(rssiNeedsEmit(-72, -70, 6, 6, 3)); } -static void test_rssiNeedsEmit_real_change_after_interval_emits() { - TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 10000, 3, 10000)); -} -static void test_rssiNeedsEmit_real_change_too_soon_waits() { - TEST_ASSERT_FALSE(rssiNeedsEmit(-73, -70, 6, 6, 5000, 3, 10000)); +static void test_rssiNeedsEmit_real_change_emits() { + TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 3)); } static void test_rssiNeedsEmit_either_direction() { - TEST_ASSERT_TRUE(rssiNeedsEmit(-67, -70, 6, 6, 10000, 3, 10000)); - TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 10000, 3, 10000)); + TEST_ASSERT_TRUE(rssiNeedsEmit(-67, -70, 6, 6, 3)); + TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 3)); } /********************************************************************* * RssiBroadcast ********************************************************************/ -static RssiBroadcast broadcastAt(int rssi, int channel, uint32_t at) { +static RssiBroadcast broadcastOf(int rssi, int channel) { RssiBroadcast b; - b.sent(true, rssi, channel, at); + b.sent(true, rssi, channel); return b; } +static void test_rssiBroadcast_first_status_is_due() { + // Nothing broadcast since boot, so the first status tick after connecting + // sends the signal. + RssiBroadcast b; + TEST_ASSERT_TRUE(b.due(-60, 6, 3)); +} static void test_rssiBroadcast_client_emit_keeps_baseline() { // A new client gets the current RSSI right as it drops 5 dBm. The clients // already connected must still get the change. - RssiBroadcast b = broadcastAt(-60, 6, 0); - b.sent(false, -65, 6, 20000); + RssiBroadcast b = broadcastOf(-60, 6); + b.sent(false, -65, 6); TEST_ASSERT_EQUAL_INT(-60, b.rssi); - TEST_ASSERT_EQUAL_UINT32(0, b.at); - TEST_ASSERT_TRUE(b.due(-65, 6, 20000, 3, 10000)); + TEST_ASSERT_TRUE(b.due(-65, 6, 3)); } static void test_rssiBroadcast_broadcast_moves_baseline() { - RssiBroadcast b = broadcastAt(-60, 6, 0); - b.sent(true, -65, 6, 20000); + RssiBroadcast b = broadcastOf(-60, 6); + b.sent(true, -65, 6); TEST_ASSERT_EQUAL_INT(-65, b.rssi); - TEST_ASSERT_EQUAL_UINT32(20000, b.at); - // Measured from the new baseline and too soon after it. - TEST_ASSERT_FALSE(b.due(-66, 6, 40000, 3, 10000)); - TEST_ASSERT_FALSE(b.due(-70, 6, 25000, 3, 10000)); - TEST_ASSERT_TRUE(b.due(-70, 6, 30000, 3, 10000)); + // Measured from the new baseline. + TEST_ASSERT_FALSE(b.due(-66, 6, 3)); + TEST_ASSERT_TRUE(b.due(-70, 6, 3)); } static void test_rssiBroadcast_channel_change_is_due() { - RssiBroadcast b = broadcastAt(-60, 6, 1000); - TEST_ASSERT_TRUE(b.due(-60, 11, 1000, 3, 10000)); + RssiBroadcast b = broadcastOf(-60, 6); + TEST_ASSERT_TRUE(b.due(-60, 11, 3)); } static void test_rssiBroadcast_client_emit_on_disconnect_keeps_baseline() { - RssiBroadcast b = broadcastAt(-60, 6, 0); - b.sent(false, -100, -1, 5000); + RssiBroadcast b = broadcastOf(-60, 6); + b.sent(false, -100, -1); TEST_ASSERT_EQUAL_INT(6, b.channel); - TEST_ASSERT_FALSE(b.due(-60, 6, 5000, 3, 10000)); + TEST_ASSERT_FALSE(b.due(-60, 6, 3)); } -static void test_rssiBroadcast_hold_off_across_millis_wrap() { - RssiBroadcast b = broadcastAt(-60, 6, 0xFFFFF000u); - TEST_ASSERT_FALSE(b.due(-70, 6, 0x00001000u, 3, 10000)); // 8192ms later - TEST_ASSERT_TRUE(b.due(-70, 6, 0x00002000u, 3, 10000)); // 12288ms later + +/********************************************************************* + * emitAllowed + ********************************************************************/ +static int loopTask, wifiEventTask; +static void test_emitAllowed_on_the_loop_task() { + TEST_ASSERT_TRUE(emitAllowed(&loopTask, &loopTask)); +} +static void test_emitAllowed_refuses_another_task() { + TEST_ASSERT_FALSE(emitAllowed(&wifiEventTask, &loopTask)); +} +static void test_emitAllowed_before_the_owner_is_known() { + // setup() before SocketEmitter::begin(), which is loop()'s task anyway. + TEST_ASSERT_TRUE(emitAllowed(&loopTask, nullptr)); } /********************************************************************* @@ -336,22 +346,6 @@ static void test_wifiAuthModeName_unknown_past_the_shared_values() { TEST_ASSERT_EQUAL_STRING("UNKNOWN", wifiAuthModeName(255)); } -/********************************************************************* - * 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)); -} - /********************************************************************* * LoopStats ********************************************************************/ @@ -437,14 +431,16 @@ int main(int argc, char **argv) { RUN_TEST(test_parseUTCTime_ignores_local_zone); RUN_TEST(test_rssiNeedsEmit_channel_change_always_emits); RUN_TEST(test_rssiNeedsEmit_small_change_is_ignored); - RUN_TEST(test_rssiNeedsEmit_real_change_after_interval_emits); - RUN_TEST(test_rssiNeedsEmit_real_change_too_soon_waits); + RUN_TEST(test_rssiNeedsEmit_real_change_emits); RUN_TEST(test_rssiNeedsEmit_either_direction); + RUN_TEST(test_rssiBroadcast_first_status_is_due); RUN_TEST(test_rssiBroadcast_client_emit_keeps_baseline); RUN_TEST(test_rssiBroadcast_broadcast_moves_baseline); 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_emitAllowed_on_the_loop_task); + RUN_TEST(test_emitAllowed_refuses_another_task); + RUN_TEST(test_emitAllowed_before_the_owner_is_known); 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); @@ -458,9 +454,6 @@ int main(int argc, char **argv) { RUN_TEST(test_formatBacktrace_stops_when_the_buffer_is_full); RUN_TEST(test_wifiAuthModeName_matches_the_idf_enum); RUN_TEST(test_wifiAuthModeName_unknown_past_the_shared_values); - RUN_TEST(test_heapEmitTarget_one_client_always_gets_it); - RUN_TEST(test_heapEmitTarget_broadcasts_once_per_interval); - RUN_TEST(test_heapEmitTarget_room_in_between); RUN_TEST(test_loopStats_rate_is_passes_per_second); RUN_TEST(test_loopStats_empty_window_has_no_rate); RUN_TEST(test_loopStats_keeps_the_longest_pass);