mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-31 13:52:15 +01:00
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>
200 lines
6.2 KiB
C++
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;
|
|
}
|