mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 22:02:17 +01:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
474c7c0f42
commit
25d548ef07
11 changed files with 153 additions and 117 deletions
|
|
@ -22,8 +22,6 @@ extern ESPNetwork net;
|
||||||
extern SomfyShadeController somfy;
|
extern SomfyShadeController somfy;
|
||||||
extern LoopStats loopStats;
|
extern LoopStats loopStats;
|
||||||
|
|
||||||
static unsigned long _lastHeapEmit = 0;
|
|
||||||
|
|
||||||
static bool _apScanning = false;
|
static bool _apScanning = false;
|
||||||
int connectRetries = 0;
|
int connectRetries = 0;
|
||||||
void ESPNetwork::end() {
|
void ESPNetwork::end() {
|
||||||
|
|
@ -144,13 +142,9 @@ void ESPNetwork::loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(millis() - this->lastEmit > 1500) {
|
if(millis() - this->lastEmit >= STATUS_EMIT_MS) {
|
||||||
// Post our connection status if needed.
|
|
||||||
this->lastEmit = millis();
|
this->lastEmit = millis();
|
||||||
if(this->connected()) {
|
if(this->connected()) this->emitSockets();
|
||||||
this->emitSockets();
|
|
||||||
this->lastEmit = millis();
|
|
||||||
}
|
|
||||||
resetTaskWdt(); // Make sure we do not reboot here.
|
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();
|
this->connectStart = millis();
|
||||||
return false;
|
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() {
|
void ESPNetwork::emitSockets() {
|
||||||
this->emitHeap();
|
this->emitHeap();
|
||||||
if(this->needsBroadcast ||
|
if(this->connType != conn_types_t::wifi ||
|
||||||
(this->connType == conn_types_t::wifi &&
|
this->rssiBroadcast.due(WiFi.RSSI(), WiFi.channel(), RSSI_EMIT_MIN_DBM))
|
||||||
this->rssiBroadcast.due(WiFi.RSSI(), WiFi.channel(), millis(), RSSI_EMIT_MIN_DBM, RSSI_EMIT_MIN_MS))) {
|
|
||||||
this->emitSockets(255);
|
this->emitSockets(255);
|
||||||
sockEmit.loop();
|
|
||||||
this->needsBroadcast = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
void ESPNetwork::emitSockets(uint8_t num) {
|
void ESPNetwork::emitSockets(uint8_t num) {
|
||||||
#if HAS_ETHERNET
|
#if HAS_ETHERNET
|
||||||
|
|
@ -208,7 +200,7 @@ void ESPNetwork::emitSockets(uint8_t num) {
|
||||||
json->addElem("channel", (int32_t)this->channel);
|
json->addElem("channel", (int32_t)this->channel);
|
||||||
json->endObject();
|
json->endObject();
|
||||||
sockEmit.endEmit(num);
|
sockEmit.endEmit(num);
|
||||||
this->rssiBroadcast.sent(num == 255, WiFi.RSSI(), WiFi.channel(), millis());
|
this->rssiBroadcast.sent(num == 255, WiFi.RSSI(), WiFi.channel());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
JsonSockEvent *json = sockEmit.beginEmit("wifiStrength");
|
JsonSockEvent *json = sockEmit.beginEmit("wifiStrength");
|
||||||
|
|
@ -226,12 +218,15 @@ void ESPNetwork::emitSockets(uint8_t num) {
|
||||||
json->addElem("fullduplex", false);
|
json->addElem("fullduplex", false);
|
||||||
json->endObject();
|
json->endObject();
|
||||||
sockEmit.endEmit(num);
|
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.
|
// A broadcast comes from emitSockets(), which has already sent memStatus.
|
||||||
if(num != 255) this->emitHeap(num);
|
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) {
|
void ESPNetwork::setConnected(conn_types_t connType) {
|
||||||
resetTaskWdt();
|
resetTaskWdt();
|
||||||
this->connType = connType;
|
this->connType = connType;
|
||||||
|
|
@ -287,15 +282,6 @@ void ESPNetwork::setConnected(conn_types_t connType) {
|
||||||
settings.IP.dns1 = ETH.dnsIP(0);
|
settings.IP.dns1 = ETH.dnsIP(0);
|
||||||
settings.IP.dns2 = ETH.dnsIP(1);
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -347,9 +333,7 @@ void ESPNetwork::setConnected(conn_types_t connType) {
|
||||||
}
|
}
|
||||||
else if(SSDP.isStarted) SSDP.end();
|
else if(SSDP.isStarted) SSDP.end();
|
||||||
resetTaskWdt();
|
resetTaskWdt();
|
||||||
this->emitSockets();
|
|
||||||
settings.printAvailHeap();
|
settings.printAvailHeap();
|
||||||
this->needsBroadcast = true;
|
|
||||||
}
|
}
|
||||||
#if HAS_ETHERNET
|
#if HAS_ETHERNET
|
||||||
bool ESPNetwork::connectWired() {
|
bool ESPNetwork::connectWired() {
|
||||||
|
|
@ -693,8 +677,6 @@ void ESPNetwork::emitHeap(uint8_t num) {
|
||||||
uint32_t freeHeap = ESP.getFreeHeap();
|
uint32_t freeHeap = ESP.getFreeHeap();
|
||||||
uint32_t maxHeap = ESP.getMaxAllocHeap();
|
uint32_t maxHeap = ESP.getMaxAllocHeap();
|
||||||
uint32_t minHeap = ESP.getMinFreeHeap();
|
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");
|
JsonSockEvent *json = sockEmit.beginEmit("memStatus");
|
||||||
json->beginObject();
|
json->beginObject();
|
||||||
json->addElem("max", maxHeap);
|
json->addElem("max", maxHeap);
|
||||||
|
|
@ -707,10 +689,6 @@ void ESPNetwork::emitHeap(uint8_t num) {
|
||||||
json->addElem("loopRate", loopStats.rate(millis()));
|
json->addElem("loopRate", loopStats.rate(millis()));
|
||||||
json->addElem("loopMaxUs", loopStats.maxUs());
|
json->addElem("loopMaxUs", loopStats.maxUs());
|
||||||
json->endObject();
|
json->endObject();
|
||||||
if(target == heap_emit_t::room) sockEmit.endEmitRoom(0);
|
sockEmit.endEmit(num);
|
||||||
else sockEmit.endEmit(num);
|
if(num == 255) loopStats.publish(millis());
|
||||||
if(target == heap_emit_t::broadcast) {
|
|
||||||
_lastHeapEmit = millis();
|
|
||||||
loopStats.publish(_lastHeapEmit);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,13 @@
|
||||||
#define CONNECT_TIMEOUT 20000
|
#define CONNECT_TIMEOUT 20000
|
||||||
#define SSID_SCAN_INTERVAL 600000
|
#define SSID_SCAN_INTERVAL 600000
|
||||||
// A weak signal moves a dBm or two all the time, so only broadcast real
|
// 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_DBM 3
|
||||||
#define RSSI_EMIT_MIN_MS 10000
|
// How often loop() broadcasts the status: memStatus, then wifiStrength or
|
||||||
// How often memStatus is broadcast. It is also the one event that is always
|
// ethernet. memStatus is the one event that is always sent, so this must stay
|
||||||
// sent, so it must stay well under Home Assistant's 60s receive timeout.
|
// well under Home Assistant's 60s receive timeout. A client that connects gets
|
||||||
#define HEAP_EMIT_MS 30000
|
// 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.
|
// A roaming scan only moves to another AP at least this much stronger.
|
||||||
#define ROAM_MIN_GAIN_DB 6
|
#define ROAM_MIN_GAIN_DB 6
|
||||||
class ESPNetwork {
|
class ESPNetwork {
|
||||||
|
|
@ -31,7 +32,6 @@ class ESPNetwork {
|
||||||
bool wifiFallback = false;
|
bool wifiFallback = false;
|
||||||
bool softAPOpened = false;
|
bool softAPOpened = false;
|
||||||
bool openingSoftAP = false;
|
bool openingSoftAP = false;
|
||||||
bool needsBroadcast = true;
|
|
||||||
conn_types_t connType = conn_types_t::unset;
|
conn_types_t connType = conn_types_t::unset;
|
||||||
conn_types_t connTarget = conn_types_t::unset;
|
conn_types_t connTarget = conn_types_t::unset;
|
||||||
bool connected();
|
bool connected();
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,17 @@ void BufferedJsonFormatter::setBuffer(char *b, size_t sz) {
|
||||||
size_t BufferedJsonFormatter::length() const {
|
size_t BufferedJsonFormatter::length() const {
|
||||||
return this->_len;
|
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) {
|
void JsonFormatter::beginObject(const char *name) {
|
||||||
if(name && strlen(name) > 0) this->appendElem(name);
|
if(name && strlen(name) > 0) this->appendElem(name);
|
||||||
else if(!this->_nocomma) this->_safecat(",");
|
else if(!this->_nocomma) this->_safecat(",");
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ class JsonFormatter {
|
||||||
void _appendNumber(const char *name);
|
void _appendNumber(const char *name);
|
||||||
public:
|
public:
|
||||||
bool overflowed() const { return _overflow; }
|
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
|
// Writes c as it must appear inside a JSON string (out needs 7 bytes) and
|
||||||
// returns its length.
|
// returns its length.
|
||||||
static size_t escapeChar(char c, char *out);
|
static size_t escapeChar(char c, char *out);
|
||||||
|
|
|
||||||
|
|
@ -130,19 +130,21 @@ int Timestamp::tzOffset() {
|
||||||
time(&now);
|
time(&now);
|
||||||
return Timestamp::calcTZOffset(&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;
|
if(channel != lastChannel) return true;
|
||||||
int delta = rssi > lastRssi ? rssi - lastRssi : lastRssi - rssi;
|
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 {
|
bool RssiBroadcast::due(int rssi, int channel, int minDelta) const {
|
||||||
return rssiNeedsEmit(rssi, this->rssi, channel, this->channel, now - this->at, minDelta, minMs);
|
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;
|
if(!broadcast) return;
|
||||||
this->rssi = rssi;
|
this->rssi = rssi;
|
||||||
this->channel = channel;
|
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) {
|
bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain) {
|
||||||
return !connected || apRssi >= currentRssi + minGain;
|
return !connected || apRssi >= currentRssi + minGain;
|
||||||
|
|
@ -179,11 +181,6 @@ const char *wifiAuthModeName(uint8_t authMode) {
|
||||||
default: return "UNKNOWN";
|
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) {
|
void LoopStats::record(uint32_t passUs) {
|
||||||
_passes++;
|
_passes++;
|
||||||
if(passUs > _maxUs) _maxUs = passUs;
|
if(passUs > _maxUs) _maxUs = passUs;
|
||||||
|
|
|
||||||
19
src/Utils.h
19
src/Utils.h
|
|
@ -58,30 +58,27 @@ struct rebootDelay_t {
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
// Whether a wifiStrength broadcast is worth sending: always on a channel change,
|
// 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
|
// otherwise only for an RSSI change of at least minDelta dBm.
|
||||||
// than every minMs.
|
bool rssiNeedsEmit(int rssi, int lastRssi, int channel, int lastChannel, int minDelta);
|
||||||
bool rssiNeedsEmit(int rssi, int lastRssi, int channel, int lastChannel, uint32_t sinceLastMs, int minDelta, uint32_t minMs);
|
|
||||||
// What the last wifiStrength broadcast told every client. Only broadcasts
|
// What the last wifiStrength broadcast told every client. Only broadcasts
|
||||||
// move it: an emit to one new client tells the others nothing.
|
// move it: an emit to one new client tells the others nothing.
|
||||||
struct RssiBroadcast {
|
struct RssiBroadcast {
|
||||||
int rssi = 0;
|
int rssi = 0;
|
||||||
int channel = 0;
|
int channel = 0;
|
||||||
uint32_t at = 0; // millis() of the last broadcast.
|
bool due(int rssi, int channel, int minDelta) const;
|
||||||
bool due(int rssi, int channel, uint32_t now, int minDelta, uint32_t minMs) const;
|
void sent(bool broadcast, int rssi, int channel);
|
||||||
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
|
// 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
|
// 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.
|
// a small swing doesn't make the device hop between APs.
|
||||||
bool worthRoaming(bool connected, int currentRssi, int apRssi, int minGain);
|
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
|
// 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.
|
// WAPI are the same in ESP-IDF 4.4 and 5.x.
|
||||||
const char *wifiAuthModeName(uint8_t authMode);
|
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
|
// Whether a firmware the bootloader is holding for verification after an
|
||||||
// update has proven itself: it has run for minUptimeMs and the network is
|
// update has proven itself: it has run for minUptimeMs and the network is
|
||||||
// up. Until then a reset rolls back to the previous firmware.
|
// up. Until then a reset rolls back to the previous firmware.
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,6 @@ void Web::handleDiscovery(AsyncWebServerRequest *request) {
|
||||||
if(request->method() == HTTP_POST || request->method() == HTTP_GET) {
|
if(request->method() == HTTP_POST || request->method() == HTTP_GET) {
|
||||||
ESP_LOGD(TAG, "Async Discovery Requested");
|
ESP_LOGD(TAG, "Async Discovery Requested");
|
||||||
sendSnapshot(request, snapshot_doc_t::discovery, 0, discoveryHeader);
|
sendSnapshot(request, snapshot_doc_t::discovery, 0, discoveryHeader);
|
||||||
net.needsBroadcast = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
request->send(500, _encoding_text, "Invalid http method");
|
request->send(500, _encoding_text, "Invalid http method");
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "ESPNetwork.h"
|
#include "ESPNetwork.h"
|
||||||
#include "GitOTA.h"
|
#include "GitOTA.h"
|
||||||
#include "Timing.h"
|
#include "Timing.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
static const char *TAG = "Sockets";
|
static const char *TAG = "Sockets";
|
||||||
|
|
||||||
|
|
@ -92,20 +93,36 @@ void SocketEmitter::startup() {
|
||||||
|
|
||||||
}
|
}
|
||||||
void SocketEmitter::begin() {
|
void SocketEmitter::begin() {
|
||||||
|
// Called from setup(), which runs on loop()'s task.
|
||||||
|
this->_owner = xTaskGetCurrentTaskHandle();
|
||||||
ws.onEvent(SocketEmitter::wsEvent);
|
ws.onEvent(SocketEmitter::wsEvent);
|
||||||
wsServer.addHandler(&ws);
|
wsServer.addHandler(&ws);
|
||||||
wsServer.begin();
|
wsServer.begin();
|
||||||
ESP_LOGI(TAG, "Socket Server Started...");
|
ESP_LOGI(TAG, "Socket Server Started...");
|
||||||
}
|
}
|
||||||
|
bool SocketEmitter::onLoopTask() const { return emitAllowed(xTaskGetCurrentTaskHandle(), this->_owner); }
|
||||||
void SocketEmitter::loop() {
|
void SocketEmitter::loop() {
|
||||||
|
if(!this->onLoopTask()) {
|
||||||
|
ESP_LOGE(TAG, "Socket loop off loop() from %s, skipped", pcTaskGetName(nullptr));
|
||||||
|
return;
|
||||||
|
}
|
||||||
ws.cleanupClients();
|
ws.cleanupClients();
|
||||||
this->initClients();
|
this->initClients();
|
||||||
}
|
}
|
||||||
JsonSockEvent *SocketEmitter::beginEmit(const char *evt) {
|
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));
|
this->json.beginEvent(&ws, evt, g_response, sizeof(g_response));
|
||||||
return &this->json;
|
return &this->json;
|
||||||
}
|
}
|
||||||
void SocketEmitter::endEmit(uint8_t num) {
|
void SocketEmitter::endEmit(uint8_t num) {
|
||||||
|
// beginEmit() has logged it.
|
||||||
|
if(!this->onLoopTask()) return;
|
||||||
if(num == 255) {
|
if(num == 255) {
|
||||||
this->json.endEvent(0);
|
this->json.endEvent(0);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -115,6 +132,7 @@ void SocketEmitter::endEmit(uint8_t num) {
|
||||||
resetTaskWdt();
|
resetTaskWdt();
|
||||||
}
|
}
|
||||||
void SocketEmitter::endEmitRoom(uint8_t room) {
|
void SocketEmitter::endEmitRoom(uint8_t room) {
|
||||||
|
if(!this->onLoopTask()) return;
|
||||||
if(room < SOCK_MAX_ROOMS) {
|
if(room < SOCK_MAX_ROOMS) {
|
||||||
room_t *r = &this->rooms[room];
|
room_t *r = &this->rooms[room];
|
||||||
for(uint8_t i = 0; i < sizeof(r->clients); i++) {
|
for(uint8_t i = 0; i < sizeof(r->clients); i++) {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,11 @@ class SocketEmitter {
|
||||||
protected:
|
protected:
|
||||||
uint8_t newclients = 0;
|
uint8_t newclients = 0;
|
||||||
uint8_t newClients[5] = {255,255,255,255,255};
|
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);
|
void delayInit(uint8_t num);
|
||||||
public:
|
public:
|
||||||
JsonSockEvent json;
|
JsonSockEvent json;
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,39 @@ static void test_setBuffer_resets_length() {
|
||||||
TEST_ASSERT_EQUAL_STRING("", storage);
|
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) {
|
int main(int argc, char **argv) {
|
||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
|
|
@ -216,5 +249,7 @@ int main(int argc, char **argv) {
|
||||||
RUN_TEST(test_length_matches_what_was_written);
|
RUN_TEST(test_length_matches_what_was_written);
|
||||||
RUN_TEST(test_refused_append_leaves_length_alone);
|
RUN_TEST(test_refused_append_leaves_length_alone);
|
||||||
RUN_TEST(test_setBuffer_resets_length);
|
RUN_TEST(test_setBuffer_resets_length);
|
||||||
|
RUN_TEST(test_discard_writes_nothing);
|
||||||
|
RUN_TEST(test_setBuffer_after_discard_writes_again);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,63 +181,73 @@ static void test_parseUTCTime_ignores_local_zone() {
|
||||||
* rssiNeedsEmit
|
* rssiNeedsEmit
|
||||||
********************************************************************/
|
********************************************************************/
|
||||||
static void test_rssiNeedsEmit_channel_change_always_emits() {
|
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() {
|
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() {
|
static void test_rssiNeedsEmit_real_change_emits() {
|
||||||
TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 10000, 3, 10000));
|
TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 3));
|
||||||
}
|
|
||||||
static void test_rssiNeedsEmit_real_change_too_soon_waits() {
|
|
||||||
TEST_ASSERT_FALSE(rssiNeedsEmit(-73, -70, 6, 6, 5000, 3, 10000));
|
|
||||||
}
|
}
|
||||||
static void test_rssiNeedsEmit_either_direction() {
|
static void test_rssiNeedsEmit_either_direction() {
|
||||||
TEST_ASSERT_TRUE(rssiNeedsEmit(-67, -70, 6, 6, 10000, 3, 10000));
|
TEST_ASSERT_TRUE(rssiNeedsEmit(-67, -70, 6, 6, 3));
|
||||||
TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 10000, 3, 10000));
|
TEST_ASSERT_TRUE(rssiNeedsEmit(-73, -70, 6, 6, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************
|
/*********************************************************************
|
||||||
* RssiBroadcast
|
* RssiBroadcast
|
||||||
********************************************************************/
|
********************************************************************/
|
||||||
static RssiBroadcast broadcastAt(int rssi, int channel, uint32_t at) {
|
static RssiBroadcast broadcastOf(int rssi, int channel) {
|
||||||
RssiBroadcast b;
|
RssiBroadcast b;
|
||||||
b.sent(true, rssi, channel, at);
|
b.sent(true, rssi, channel);
|
||||||
return b;
|
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() {
|
static void test_rssiBroadcast_client_emit_keeps_baseline() {
|
||||||
// A new client gets the current RSSI right as it drops 5 dBm. The clients
|
// A new client gets the current RSSI right as it drops 5 dBm. The clients
|
||||||
// already connected must still get the change.
|
// already connected must still get the change.
|
||||||
RssiBroadcast b = broadcastAt(-60, 6, 0);
|
RssiBroadcast b = broadcastOf(-60, 6);
|
||||||
b.sent(false, -65, 6, 20000);
|
b.sent(false, -65, 6);
|
||||||
TEST_ASSERT_EQUAL_INT(-60, b.rssi);
|
TEST_ASSERT_EQUAL_INT(-60, b.rssi);
|
||||||
TEST_ASSERT_EQUAL_UINT32(0, b.at);
|
TEST_ASSERT_TRUE(b.due(-65, 6, 3));
|
||||||
TEST_ASSERT_TRUE(b.due(-65, 6, 20000, 3, 10000));
|
|
||||||
}
|
}
|
||||||
static void test_rssiBroadcast_broadcast_moves_baseline() {
|
static void test_rssiBroadcast_broadcast_moves_baseline() {
|
||||||
RssiBroadcast b = broadcastAt(-60, 6, 0);
|
RssiBroadcast b = broadcastOf(-60, 6);
|
||||||
b.sent(true, -65, 6, 20000);
|
b.sent(true, -65, 6);
|
||||||
TEST_ASSERT_EQUAL_INT(-65, b.rssi);
|
TEST_ASSERT_EQUAL_INT(-65, b.rssi);
|
||||||
TEST_ASSERT_EQUAL_UINT32(20000, b.at);
|
// Measured from the new baseline.
|
||||||
// Measured from the new baseline and too soon after it.
|
TEST_ASSERT_FALSE(b.due(-66, 6, 3));
|
||||||
TEST_ASSERT_FALSE(b.due(-66, 6, 40000, 3, 10000));
|
TEST_ASSERT_TRUE(b.due(-70, 6, 3));
|
||||||
TEST_ASSERT_FALSE(b.due(-70, 6, 25000, 3, 10000));
|
|
||||||
TEST_ASSERT_TRUE(b.due(-70, 6, 30000, 3, 10000));
|
|
||||||
}
|
}
|
||||||
static void test_rssiBroadcast_channel_change_is_due() {
|
static void test_rssiBroadcast_channel_change_is_due() {
|
||||||
RssiBroadcast b = broadcastAt(-60, 6, 1000);
|
RssiBroadcast b = broadcastOf(-60, 6);
|
||||||
TEST_ASSERT_TRUE(b.due(-60, 11, 1000, 3, 10000));
|
TEST_ASSERT_TRUE(b.due(-60, 11, 3));
|
||||||
}
|
}
|
||||||
static void test_rssiBroadcast_client_emit_on_disconnect_keeps_baseline() {
|
static void test_rssiBroadcast_client_emit_on_disconnect_keeps_baseline() {
|
||||||
RssiBroadcast b = broadcastAt(-60, 6, 0);
|
RssiBroadcast b = broadcastOf(-60, 6);
|
||||||
b.sent(false, -100, -1, 5000);
|
b.sent(false, -100, -1);
|
||||||
TEST_ASSERT_EQUAL_INT(6, b.channel);
|
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
|
* emitAllowed
|
||||||
TEST_ASSERT_TRUE(b.due(-70, 6, 0x00002000u, 3, 10000)); // 12288ms later
|
********************************************************************/
|
||||||
|
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));
|
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
|
* LoopStats
|
||||||
********************************************************************/
|
********************************************************************/
|
||||||
|
|
@ -437,14 +431,16 @@ int main(int argc, char **argv) {
|
||||||
RUN_TEST(test_parseUTCTime_ignores_local_zone);
|
RUN_TEST(test_parseUTCTime_ignores_local_zone);
|
||||||
RUN_TEST(test_rssiNeedsEmit_channel_change_always_emits);
|
RUN_TEST(test_rssiNeedsEmit_channel_change_always_emits);
|
||||||
RUN_TEST(test_rssiNeedsEmit_small_change_is_ignored);
|
RUN_TEST(test_rssiNeedsEmit_small_change_is_ignored);
|
||||||
RUN_TEST(test_rssiNeedsEmit_real_change_after_interval_emits);
|
RUN_TEST(test_rssiNeedsEmit_real_change_emits);
|
||||||
RUN_TEST(test_rssiNeedsEmit_real_change_too_soon_waits);
|
|
||||||
RUN_TEST(test_rssiNeedsEmit_either_direction);
|
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_client_emit_keeps_baseline);
|
||||||
RUN_TEST(test_rssiBroadcast_broadcast_moves_baseline);
|
RUN_TEST(test_rssiBroadcast_broadcast_moves_baseline);
|
||||||
RUN_TEST(test_rssiBroadcast_channel_change_is_due);
|
RUN_TEST(test_rssiBroadcast_channel_change_is_due);
|
||||||
RUN_TEST(test_rssiBroadcast_client_emit_on_disconnect_keeps_baseline);
|
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_needs_the_full_gain);
|
||||||
RUN_TEST(test_worthRoaming_never_to_a_weaker_ap);
|
RUN_TEST(test_worthRoaming_never_to_a_weaker_ap);
|
||||||
RUN_TEST(test_worthRoaming_any_ap_while_disconnected);
|
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_formatBacktrace_stops_when_the_buffer_is_full);
|
||||||
RUN_TEST(test_wifiAuthModeName_matches_the_idf_enum);
|
RUN_TEST(test_wifiAuthModeName_matches_the_idf_enum);
|
||||||
RUN_TEST(test_wifiAuthModeName_unknown_past_the_shared_values);
|
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_rate_is_passes_per_second);
|
||||||
RUN_TEST(test_loopStats_empty_window_has_no_rate);
|
RUN_TEST(test_loopStats_empty_window_has_no_rate);
|
||||||
RUN_TEST(test_loopStats_keeps_the_longest_pass);
|
RUN_TEST(test_loopStats_keeps_the_longest_pass);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue