ESPSomfy-RTS/src/Utils.cpp
cjkas 25d548ef07 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>
2026-10-21 15:27:21 +02:00

200 lines
6.2 KiB
C++

#include <Arduino.h>
#include <time.h>
#include "Utils.h"
/*********************************************************************
* Timestamp class members
********************************************************************/
unsigned long Timestamp::epoch() {
struct tm tmNow;
time_t now;
if(!getLocalTime(&tmNow,50)) return 0;
time(&now);
return now;
}
time_t Timestamp::now() {
struct tm tmNow;
getLocalTime(&tmNow,50);
return mktime(&tmNow);
}
time_t Timestamp::getUTC() {
time_t t;
time(&t);
return t;
}
time_t Timestamp::mkUTCTime(struct tm *dt) {
time_t tsBadLocal = mktime(dt);
struct tm tmUTC;
struct tm tmLocal;
gmtime_r(&tsBadLocal, &tmUTC);
localtime_r(&tsBadLocal, &tmLocal);
time_t tsBadUTC = mktime(&tmUTC);
time_t tsLocal = mktime(&tmLocal);
time_t tsLocalOffset = tsLocal - tsBadUTC;
return tsBadLocal + tsLocalOffset;
}
time_t Timestamp::parseUTCTime(const char *buff) {
struct tm dt;
dt.tm_hour = 0;
dt.tm_mday = 0;
dt.tm_mon = 0;
dt.tm_year = 0;
dt.tm_wday = 0;
dt.tm_yday = 0;
dt.tm_isdst = false;
char num[5];
uint8_t i = 0;
memset(num, 0x00, sizeof(num));
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
char ch = buff[i++];
if(ch == '-') break;
if(!isdigit(ch)) continue;
else num[j++] = ch;
}
dt.tm_year = atoi(num)-1900;
memset(num, 0x00, sizeof(num));
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
char ch = buff[i++];
if(ch == '-') break;
if(!isdigit(ch)) continue;
else num[j++] = ch;
}
dt.tm_mon = atoi(num)-1;
memset(num, 0x00, sizeof(num));
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
char ch = buff[i++];
if(ch == '-' || ch == 'T' || ch == 't') break;
if(!isdigit(ch)) continue;
else num[j++] = ch;
}
dt.tm_mday = atoi(num);
memset(num, 0x00, sizeof(num));
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
char ch = buff[i++];
if(ch == '-' || ch == ':') break;
if(!isdigit(ch)) continue;
else num[j++] = ch;
}
dt.tm_hour = atoi(num);
memset(num, 0x00, sizeof(num));
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
char ch = buff[i++];
if(ch == '-' || ch == ':') break;
if(!isdigit(ch)) continue;
else num[j++] = ch;
}
dt.tm_min = atoi(num);
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
char ch = buff[i++];
if(ch == '-' || ch == ':' || ch == 'Z') break;
if(!isdigit(ch)) continue;
else num[j++] = ch;
}
dt.tm_sec = atoi(num);
//Serial.printf("Y:%d M:%d D:%d H:%d M:%d S:%d\n", dt.tm_year, dt.tm_mon, dt.tm_mday, dt.tm_hour, dt.tm_min, dt.tm_sec);
return Timestamp::mkUTCTime(&dt);
}
time_t Timestamp::getUTC(time_t t) {
tm tmUTC;
gmtime_r(&t, &tmUTC);
return mktime(&tmUTC);
}
char * Timestamp::getISOTime() { return this->getISOTime(this->getUTC()); }
char * Timestamp::getISOTime(time_t epoch) {
struct tm *dt = localtime((time_t *)&epoch);
return this->formatISO(dt, this->tzOffset());
}
char * Timestamp::formatISO(struct tm *dt, int tz) {
int tzHrs = floor(tz/100);
int tzMin = tz - (tzHrs * 100);
int ms = millis() % 1000;
snprintf(this->_timeBuffer, sizeof(this->_timeBuffer), "%04d-%02d-%02dT%02d:%02d:%02d.%03d%s%02d%02d",
dt->tm_year + 1900, dt->tm_mon + 1, dt->tm_mday, dt->tm_hour, dt->tm_min, dt->tm_sec, ms, tzHrs < 0 ? "-" : "+", abs(tzHrs), abs(tzMin));
return this->_timeBuffer;
}
int Timestamp::calcTZOffset(time_t *dt) {
tm tmLocal, tmUTC;
gmtime_r(dt, &tmUTC);
localtime_r(dt, &tmLocal);
long diff = mktime(&tmLocal) - mktime(&tmUTC);
if(tmLocal.tm_isdst) diff += 3600;
// Encoded as hhmm, e.g. +05:30 is 530 and -03:30 is -330.
int hrs = (int)((diff/3600) * 100);
int mins = (int)((diff % 3600) / 60);
return hrs + mins;
}
int Timestamp::tzOffset() {
time_t now;
time(&now);
return Timestamp::calcTZOffset(&now);
}
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;
}
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) {
if(!broadcast) return;
this->rssi = rssi;
this->channel = channel;
}
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;
}
bool otaProven(bool networkUp, unsigned long uptimeMs, unsigned long minUptimeMs) {
return networkUp && uptimeMs >= minUptimeMs;
}
void releaseAssetName(char *buf, size_t size, const char *kind, const char *chipModel) {
snprintf(buf, size, "SomfyController.%s.esp32%s.bin", kind, chipModel);
}
void formatBacktrace(char *buf, size_t size, const uint32_t *bt, size_t maxFrames, uint32_t depth) {
// depth comes from flash. A half-written dump reads back as 0xa5a5a5a5,
// so never trust it past bt[] or the buffer.
buf[0] = '\0';
size_t pos = 0;
size_t frames = depth < maxFrames ? depth : maxFrames;
for (size_t i = 0; i < frames && pos < size - 1; i++) {
int n = snprintf(buf + pos, size - pos, " 0x%08x", (unsigned int)bt[i]);
if (n < 0) break;
pos += (size_t)n;
}
}
const char *wifiAuthModeName(uint8_t authMode) {
switch(authMode) {
case 0: return "OPEN";
case 1: return "WEP";
case 2: return "WPA";
case 3: return "WPA2";
case 4: return "WPA/WPA2";
case 5: return "WPA2-ENT";
case 6: return "WPA3";
case 7: return "WPA2/WPA3";
case 8: return "WAPI";
default: return "UNKNOWN";
}
}
void LoopStats::record(uint32_t passUs) {
_passes++;
if(passUs > _maxUs) _maxUs = passUs;
}
uint32_t LoopStats::rate(uint32_t nowMs) const {
// The unsigned subtraction keeps the window right across a millis() wrap.
const uint32_t elapsedMs = nowMs - _startMs;
if(elapsedMs == 0) return 0;
return (uint32_t)((uint64_t)_passes * 1000 / elapsedMs);
}
void LoopStats::publish(uint32_t nowMs) {
_publishedRate.store(this->rate(nowMs), std::memory_order_relaxed);
_publishedMaxUs.store(_maxUs, std::memory_order_relaxed);
_passes = 0;
_maxUs = 0;
_startMs = nowMs;
}