From 40c4fdd2d1f2a9ad76efde0d8e4607d77ab9b1df Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 24 Sep 2026 11:28:14 +0200 Subject: [PATCH 1/5] move hardcoded consts to header files --- project-words.txt | 1 - src/ESP32React/UploadFileService.cpp | 2 +- src/core/analogsensor.h | 2 ++ src/core/emsdevice.cpp | 4 +-- src/core/emsdevice.h | 3 ++ src/core/emsesp.cpp | 4 +-- src/core/emsesp.h | 2 ++ src/core/httpClient.cpp | 6 ++-- src/core/httpClient.h | 8 +++++ src/core/mqtt.cpp | 2 +- src/core/mqtt.h | 5 +-- src/core/system.cpp | 46 ++++++++++++---------------- src/core/system.h | 15 +++++++++ src/core/telegram.cpp | 2 +- src/core/telegram.h | 3 ++ src/web/WebLogService.cpp | 4 +-- src/web/WebLogService.h | 5 +++ 17 files changed, 72 insertions(+), 42 deletions(-) diff --git a/project-words.txt b/project-words.txt index 0e8f7a52e..1a2a9624e 100644 --- a/project-words.txt +++ b/project-words.txt @@ -786,7 +786,6 @@ negcond negheat nein nennvolumentstrom -neopixel netcmp netflowtemp netif diff --git a/src/ESP32React/UploadFileService.cpp b/src/ESP32React/UploadFileService.cpp index a10266192..e9fa36ed4 100644 --- a/src/ESP32React/UploadFileService.cpp +++ b/src/ESP32React/UploadFileService.cpp @@ -71,7 +71,7 @@ void UploadFileService::handleUpload(AsyncWebServerRequest * request, const Stri _is_filesystem = true; _md5[0] = '\0'; // clear any stale md5 so Update.end() doesn't compare against it _md5_applied = false; - } else if ((extension == "bin") && (filesize > 1000000)) { + } else if ((extension == "bin") && (filesize >= emsesp::System::MIN_FIRMWARE_SIZE)) { _is_firmware = true; } else if (extension == "json") { _md5[0] = '\0'; // clear md5 diff --git a/src/core/analogsensor.h b/src/core/analogsensor.h index e44e48b7d..b0020e7b9 100644 --- a/src/core/analogsensor.h +++ b/src/core/analogsensor.h @@ -189,8 +189,10 @@ class AnalogSensor { static constexpr double T25 = 298.15; static constexpr double R0 = 100000; static constexpr double Rt = 60000; + static constexpr uint32_t NTC_VREF_MV = 3300; // NTC voltage divider supply (in mV) - 3.3 V static constexpr uint8_t MAX_SENSORS = 20; static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500; + static constexpr uint32_t FREQ_ZERO_TIMEOUT_US = 10000000; // report 0 Hz if no edge arrives for this long (in us) - 10 seconds static uuid::log::Logger logger_; void remove_ha_topic(const int8_t type, const uint8_t id) const; diff --git a/src/core/emsdevice.cpp b/src/core/emsdevice.cpp index 43d9a4ed3..dc4ecdd2c 100644 --- a/src/core/emsdevice.cpp +++ b/src/core/emsdevice.cpp @@ -2281,8 +2281,8 @@ bool EMSdevice::handle_telegram(const std::shared_ptr & telegram EMSESP::logger().debug("This telegram (%s) is not recognized by the EMS bus", tf.telegram_type_name_); #endif // removing fetch after start causes issue: https://github.com/emsesp/EMS-ESP32/issues/1420 - // continue retry the first 5 minutes, then disable (added 15.3.2024) - if (uuid::get_uptime_sec() > 600) { + // continue retrying for a while after boot, then disable (added 15.3.2024) + if (uuid::get_uptime_sec() > FETCH_RETRY_DURATION_SEC) { tf.fetch_ = false; } return false; diff --git a/src/core/emsdevice.h b/src/core/emsdevice.h index fa21a8744..8b8c640fe 100644 --- a/src/core/emsdevice.h +++ b/src/core/emsdevice.h @@ -441,6 +441,9 @@ class EMSdevice { static constexpr uint8_t EMS_DEVICES_MAX_TELEGRAMS = 20; + // keep fetching telegrams the bus master doesn't recognise for this long after boot, then give up (in seconds) - 10 minutes + static constexpr uint32_t FETCH_RETRY_DURATION_SEC = 600; + // static device IDs static constexpr uint8_t EMS_DEVICE_ID_BOILER = 0x08; // fixed device_id for Master Boiler/UBA static constexpr uint8_t EMS_DEVICE_ID_HS1 = 0x70; // fixed device_id for 1st. Cascade Boiler/UBA diff --git a/src/core/emsesp.cpp b/src/core/emsesp.cpp index e0d5eb3d5..c4767f9ac 100644 --- a/src/core/emsesp.cpp +++ b/src/core/emsesp.cpp @@ -1978,12 +1978,12 @@ void EMSESP::loop() { } } - // reset status after 5 Minutes + // reset status after a timeout if (EMSESP::system_.systemStatus() != SYSTEM_STATUS::SYSTEM_STATUS_NORMAL) { static uint32_t starttime = 0; if (starttime == 0) { starttime = uuid::get_uptime_ms(); - } else if (uuid::get_uptime_ms() - starttime > 300000) { + } else if (uuid::get_uptime_ms() - starttime > SYSTEM_STATUS_RESET_TIMEOUT) { starttime = 0; EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_NORMAL); } diff --git a/src/core/emsesp.h b/src/core/emsesp.h index 3111395ff..06382ba85 100644 --- a/src/core/emsesp.h +++ b/src/core/emsesp.h @@ -291,6 +291,8 @@ class EMSESP { static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute static constexpr uint8_t EMS_WAIT_KM_TIMEOUT = 60; // wait one minute + static constexpr uint32_t SYSTEM_STATUS_RESET_TIMEOUT = 300000; // return a non-normal system status to normal after this (in ms) - 5 minutes + struct Device_record { uint8_t product_id; EMSdevice::DeviceType device_type; diff --git a/src/core/httpClient.cpp b/src/core/httpClient.cpp index b46fe85ab..29f00eba8 100644 --- a/src/core/httpClient.cpp +++ b/src/core/httpClient.cpp @@ -66,13 +66,13 @@ int HttpClient::request(std::string url, const std::string & method, const std:: ssl_client->setInsecure(); // Notes: with root CA we should set here: ssl_client->setCACert(rootCACert); // 1 KB RX buffer is fine for small JSON-style endpoints used by the scheduler/shunting-yard but it is NOT enough for servers that send full-size TLS records (>1 KB) - ssl_client->setBufferSizes(16384, 1024); - ssl_client->setSessionTimeout(120); // Set the timeout in seconds (>=120 seconds) + ssl_client->setBufferSizes(TLS_RX_BUFFER_SIZE, TLS_TX_BUFFER_SIZE); + ssl_client->setSessionTimeout(TLS_SESSION_TIMEOUT_S); } // WiFiClient is NetworkClient, which declares no setTimeout() of its own - calling it binds to // Stream::setTimeout() and only affects readBytes(), leaving the socket on the core's 3s default basic_client->setConnectionTimeout(CONNECT_TIMEOUT_MS); - ssl_client->setTimeout(5); // seconds, drives BearSSL only - unused on the plain HTTP path + ssl_client->setTimeout(TLS_READ_TIMEOUT_S); // drives BearSSL only - unused on the plain HTTP path ssl_client->setClient(basic_client, is_https); // enableSSL = false for plain HTTP const uint16_t port = is_https ? 443 : 80; diff --git a/src/core/httpClient.h b/src/core/httpClient.h index fe30167ab..9dd5423f9 100644 --- a/src/core/httpClient.h +++ b/src/core/httpClient.h @@ -29,6 +29,14 @@ class HttpClient { public: static int request(std::string url, const std::string & method, const std::string & value, JsonObjectConst headers, std::string & result); + // ESP_SSLClient (BearSSL) settings, shared by every TLS client in the project. + // The RX buffer must hold one full TLS record: servers such as GitHub's CDN send up-to-16 KB + // records and don't negotiate max_fragment_length, so a smaller buffer can't decode the body + static constexpr size_t TLS_RX_BUFFER_SIZE = 16384; + static constexpr size_t TLS_TX_BUFFER_SIZE = 1024; + static constexpr uint32_t TLS_SESSION_TIMEOUT_S = 120; // in seconds, the library minimum + static constexpr uint32_t TLS_READ_TIMEOUT_S = 5; // in seconds, not ms like the socket timeouts + private: static constexpr uint32_t CONNECT_TIMEOUT_MS = 5000; // TCP connect, the core defaults to 3s static constexpr uint32_t FIRST_BYTE_TIMEOUT_MS = 8000; // how long the server may take to start replying diff --git a/src/core/mqtt.cpp b/src/core/mqtt.cpp index c58952318..25223c9d4 100644 --- a/src/core/mqtt.cpp +++ b/src/core/mqtt.cpp @@ -643,7 +643,7 @@ bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, con // check free mem #ifndef EMSESP_STANDALONE // if (ESP.getFreeHeap() < 60 * 1024 || ESP.getMaxAllocHeap() < 40 * 1024) { - if (heap_caps_get_free_size(MALLOC_CAP_8BIT) < 60 * 1024) { // checks free Heap+PSRAM + if (heap_caps_get_free_size(MALLOC_CAP_8BIT) < MQTT_MIN_FREE_HEAP) { // checks free Heap+PSRAM if (operation == Operation::PUBLISH) { mqtt_message_id_++; mqtt_publish_fails_++; diff --git a/src/core/mqtt.h b/src/core/mqtt.h index 81d1aed7f..5e8266030 100644 --- a/src/core/mqtt.h +++ b/src/core/mqtt.h @@ -61,9 +61,10 @@ class Mqtt { enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE }; enum NestedFormat : uint8_t { NESTED = 1, SINGLE }; - static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore + static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // fixed, not a user setting anymore static constexpr uint16_t MQTT_QUEUE_MAX_SIZE = 300; - static constexpr uint32_t MQTT_DISCONNECT_TIMEOUT = 1000; // max ms to flush a graceful disconnect on non-PSRAM boards + static constexpr uint32_t MQTT_DISCONNECT_TIMEOUT = 1000; // max ms to flush a graceful disconnect on non-PSRAM boards + static constexpr uint32_t MQTT_MIN_FREE_HEAP = 60 * 1024; // don't queue any MQTT message below this much free heap+PSRAM (in bytes) - 60 KB static void on_connect(); static void on_disconnect(espMqttClientTypes::DisconnectReason reason); diff --git a/src/core/system.cpp b/src/core/system.cpp index 8ffdede52..a2860bfc3 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -33,6 +33,7 @@ #include "firmwareVersion.h" #include "shuntingYard.h" // for compute() used by the message and sendmail commands +#include "httpClient.h" // for the shared TLS client settings #if defined(EMSESP_TEST) #include "../test/test.h" @@ -218,7 +219,7 @@ bool System::command_sendmail(const char * value, const int8_t) { ESP_SSLClient * ssl_client = nullptr; SMTPClient * smtp = nullptr; - basic_client->setTimeout(5000); + basic_client->setTimeout(SMTP_TIMEOUT_MS); const bool implicit_ssl = (security == EMAIL_SECURITY::SSL); const bool start_tls = (security == EMAIL_SECURITY::STARTTLS); @@ -229,8 +230,8 @@ bool System::command_sendmail(const char * value, const int8_t) { } else { ssl_client = new ESP_SSLClient; ssl_client->setInsecure(); - ssl_client->setBufferSizes(16384, 1024); - ssl_client->setTimeout(5); + ssl_client->setBufferSizes(HttpClient::TLS_RX_BUFFER_SIZE, HttpClient::TLS_TX_BUFFER_SIZE); + ssl_client->setTimeout(HttpClient::TLS_READ_TIMEOUT_S); // enableSSL is baked in at setClient() — true only for implicit SSL (port 465) ssl_client->setClient(basic_client, implicit_ssl); if (start_tls) { @@ -3093,8 +3094,7 @@ void System::ntp_connected(bool b) { // get NTP status bool System::ntp_connected() { - // timeout 2 hours, ntp sync is normally every hour. - if ((uuid::get_uptime_sec() - ntp_last_check_ > 7201) && ntp_connected_) { + if ((uuid::get_uptime_sec() - ntp_last_check_ > NTP_TIMEOUT_SEC) && ntp_connected_) { ntp_connected(false); } @@ -3200,19 +3200,13 @@ bool System::uploadFirmwareURL(const char * url) { if (is_https) { ssl_client.setInsecure(); // no CA validation, matches the rest of the project - // BearSSL needs a receive buffer large enough to hold one full TLS record. - // GitHub's release-assets CDN sends standard up-to-16 KB records and does NOT - // negotiate max_fragment_length, so a small (e.g. 1 KB) RX buffer makes the - // body unreadable (headers still fit one small record, hence Content-Length - // looks fine, but the first body record cannot be decoded). 16384 + overhead - // is the safe value the library itself uses by default; we go a bit smaller - // to be friendlier to 4 MB / no-PSRAM boards while still big enough for any - // record the CDN actually sends in practice. - ssl_client.setBufferSizes(16384, 1024); - ssl_client.setSessionTimeout(120); + // a small (e.g. 1 KB) RX buffer makes the body unreadable: the headers still fit one small + // record, hence Content-Length looks fine, but the first body record cannot be decoded + ssl_client.setBufferSizes(HttpClient::TLS_RX_BUFFER_SIZE, HttpClient::TLS_TX_BUFFER_SIZE); + ssl_client.setSessionTimeout(HttpClient::TLS_SESSION_TIMEOUT_S); } - basic_client.setTimeout(15000); // socket-level read timeout - ssl_client.setTimeout(15); // Stream::readBytes timeout used by Update + basic_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S * 1000); // socket-level read timeout, in ms + ssl_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S); // Stream::readBytes timeout used by Update ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP const uint16_t port = is_https ? 443 : 80; @@ -3248,9 +3242,9 @@ bool System::uploadFirmwareURL(const char * url) { ssl_client.println("Connection: close"); ssl_client.print("\r\n"); - // wait for the first byte (up to 8s, matching the previous HTTP timeout) + // wait for the first byte (matching the previous HTTP timeout) uint32_t ms = millis(); - while (ssl_client.connected() && !ssl_client.available() && millis() - ms < 8000) { + while (ssl_client.connected() && !ssl_client.available() && millis() - ms < FIRMWARE_UPLOAD_RESPONSE_TIMEOUT) { delay(1); } @@ -3326,7 +3320,7 @@ bool System::uploadFirmwareURL(const char * url) { // wait for the first byte of the body so the read loop sees real data // (headers and body may arrive in separate TLS records) uint32_t body_wait = millis(); - while (ssl_client.connected() && !ssl_client.available() && millis() - body_wait < 8000) { + while (ssl_client.connected() && !ssl_client.available() && millis() - body_wait < FIRMWARE_UPLOAD_RESPONSE_TIMEOUT) { delay(1); } if (!ssl_client.available()) { @@ -3341,7 +3335,7 @@ bool System::uploadFirmwareURL(const char * url) { } // check we have a valid size - if (firmware_size < 1677721) { // 1.6MB or greater is required + if (firmware_size < (int)MIN_FIRMWARE_SIZE) { LOG_ERROR("Firmware upload failed - invalid size"); return false; // error } @@ -3360,9 +3354,7 @@ bool System::uploadFirmwareURL(const char * url) { EMSESP::system_.systemStatus(SYSTEM_STATUS::SYSTEM_STATUS_UPLOADING); // explicit chunked read loop instead of Update.writeStream(): - constexpr size_t CHUNK_SIZE = 1024; - constexpr uint32_t READ_TIMEOUT_MS = 30000; // overall stall timeout per chunk - uint8_t buf[CHUNK_SIZE]; + uint8_t buf[FIRMWARE_UPLOAD_CHUNK_SIZE]; size_t total_read = 0; bool magic_ok = false; int last_pct = -1; @@ -3386,7 +3378,7 @@ bool System::uploadFirmwareURL(const char * url) { if (!ssl_client.connected()) { break; } - if (millis() - wait_start > READ_TIMEOUT_MS) { + if (millis() - wait_start > FIRMWARE_UPLOAD_STALL_TIMEOUT) { break; } // also bail out promptly if a cancel arrives mid-stall @@ -3407,8 +3399,8 @@ bool System::uploadFirmwareURL(const char * url) { } size_t want = (size_t)firmware_size - total_read; - if (want > CHUNK_SIZE) { - want = CHUNK_SIZE; + if (want > FIRMWARE_UPLOAD_CHUNK_SIZE) { + want = FIRMWARE_UPLOAD_CHUNK_SIZE; } size_t n = stream->readBytes(buf, want); diff --git a/src/core/system.h b/src/core/system.h index 64f2d1254..056275afc 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -370,6 +370,9 @@ class System { static constexpr uint8_t HEALTHCHECK_NO_NETWORK = (1 << 1); // 2 static constexpr uint8_t HEALTHCHECK_RESET = (1 << 7); // 128 + // smallest .bin accepted as a firmware image, for both URL and web uploads - 1.6 MB + static constexpr size_t MIN_FIRMWARE_SIZE = 1677721; + private: static uuid::log::Logger logger_; @@ -402,6 +405,18 @@ class System { static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // do a system check every 5 seconds #endif + // NTP syncs about every hour, so allow one missed sync before treating it as lost (in seconds) - 2 hours + static constexpr uint32_t NTP_TIMEOUT_SEC = 7201; + + // email + static constexpr uint32_t SMTP_TIMEOUT_MS = 5000; // socket read timeout (in ms) - 5 seconds + + // firmware upload from URL + static constexpr uint32_t FIRMWARE_UPLOAD_RESPONSE_TIMEOUT = 8000; // max wait for the first byte of the response headers/body (in ms) - 8 seconds + static constexpr uint32_t FIRMWARE_UPLOAD_READ_TIMEOUT_S = 15; // socket and Stream::readBytes timeout used by Update (in seconds) + static constexpr uint32_t FIRMWARE_UPLOAD_STALL_TIMEOUT = 30000; // abort if no data arrives for a chunk (in ms) - 30 seconds + static constexpr size_t FIRMWARE_UPLOAD_CHUNK_SIZE = 1024; // read buffer, on the stack + #ifndef EMSESP_STANDALONE static uuid::syslog::SyslogService syslog_; #endif diff --git a/src/core/telegram.cpp b/src/core/telegram.cpp index 73294d8c9..23dfc0efe 100644 --- a/src/core/telegram.cpp +++ b/src/core/telegram.cpp @@ -286,7 +286,7 @@ void TxService::send_poll() const { uint8_t TxService::get_send_id() { static uint32_t count = 0; if (!tx_telegrams_.empty() && tx_telegrams_.front().telegram_->src != ems_bus_id()) { - if (++count > 500) { // after 500 polls (~3-10 sec) there will be no master poll for this id + if (++count > MAX_POLLS_WITHOUT_MASTER) { tx_telegrams_.pop_front(); count = 0; return tx_telegrams_.empty() ? ems_bus_id() : tx_telegrams_.front().telegram_->src; diff --git a/src/core/telegram.h b/src/core/telegram.h index db8595fc0..7ebe90f07 100644 --- a/src/core/telegram.h +++ b/src/core/telegram.h @@ -440,6 +440,9 @@ class TxService : public EMSbus { static constexpr uint8_t MAXIMUM_TX_RETRIES = 3; static constexpr uint32_t POST_SEND_DELAY = 2000; + // drop a queued Tx telegram sent from another id if the master hasn't polled that id after this many polls (~3-10 seconds) + static constexpr uint32_t MAX_POLLS_WITHOUT_MASTER = 500; + private: std::deque> tx_telegrams_; // the Tx queue diff --git a/src/web/WebLogService.cpp b/src/web/WebLogService.cpp index 7b2cce9c0..007ce3c70 100644 --- a/src/web/WebLogService.cpp +++ b/src/web/WebLogService.cpp @@ -76,9 +76,9 @@ void WebLogService::operator<<(std::shared_ptr message) { limit_log_messages_ = maximum_log_messages_; } else { uint32_t maxAlloc = ESP.getMaxAllocHeap(); - if (limit_log_messages_ > 5 && maxAlloc < (50 * 1024)) { // 50k + if (limit_log_messages_ > 5 && maxAlloc < LOG_HEAP_LOW) { --limit_log_messages_; - } else if (limit_log_messages_ < maximum_log_messages_ && maxAlloc > (60 * 1024)) { // 60k + } else if (limit_log_messages_ < maximum_log_messages_ && maxAlloc > LOG_HEAP_HIGH) { ++limit_log_messages_; } } diff --git a/src/web/WebLogService.h b/src/web/WebLogService.h index 330a3ed0a..42fa884f1 100644 --- a/src/web/WebLogService.h +++ b/src/web/WebLogService.h @@ -43,6 +43,11 @@ class WebLogService : public uuid::log::Handler { virtual void operator<<(std::shared_ptr message); private: + // on boards without PSRAM, shrink the log buffer below LOW and grow it again above HIGH (max alloc heap, in bytes). + // The gap between them stops the limit flapping around a single threshold + static constexpr uint32_t LOG_HEAP_LOW = 50 * 1024; // 50 KB + static constexpr uint32_t LOG_HEAP_HIGH = 60 * 1024; // 60 KB + AsyncEventSource events_; class QueuedLogMessage { From eddc443d72e8d8c94674e629f12745fb08af4da1 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 24 Sep 2026 11:29:14 +0200 Subject: [PATCH 2/5] replacement for rgbLedWrite() to support new v6 of the WS2812B driver --- lib_standalone/Arduino.cpp | 2 - lib_standalone/Arduino.h | 2 - src/core/analogsensor.cpp | 10 ++-- src/core/led.cpp | 111 ++++++++++++++++++++++++++++++++++++- src/core/led.h | 24 ++++++++ src/emsesp_version.h | 2 +- 6 files changed, 140 insertions(+), 11 deletions(-) diff --git a/lib_standalone/Arduino.cpp b/lib_standalone/Arduino.cpp index 3932cce76..1faf07175 100644 --- a/lib_standalone/Arduino.cpp +++ b/lib_standalone/Arduino.cpp @@ -148,7 +148,5 @@ double ledcSetup(uint8_t chan, double freq, uint8_t bit_num) { void ledcAttachPin(uint8_t pin, uint8_t chan) {}; void ledcAttach(uint8_t pin, uint8_t chan, uint8_t bit_num) {}; void ledcWrite(uint8_t chan, uint32_t duty) {}; -void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val) {}; -void rgbLedWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val) {}; #endif \ No newline at end of file diff --git a/lib_standalone/Arduino.h b/lib_standalone/Arduino.h index e1f19c0fe..e01080ee8 100644 --- a/lib_standalone/Arduino.h +++ b/lib_standalone/Arduino.h @@ -83,8 +83,6 @@ double ledcSetup(uint8_t chan, double freq, uint8_t bit_num); void ledcAttachPin(uint8_t pin, uint8_t chan); void ledcAttach(uint8_t pin, uint8_t chan, uint8_t bit_num); void ledcWrite(uint8_t chan, uint32_t duty); -void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val); -void rgbLedWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val); inline int rtc_get_reset_reason(int cpu_no) { return 0; diff --git a/src/core/analogsensor.cpp b/src/core/analogsensor.cpp index ce7537140..c38286615 100644 --- a/src/core/analogsensor.cpp +++ b/src/core/analogsensor.cpp @@ -307,7 +307,7 @@ void AnalogSensor::reload(bool get_nvs) { uint8_t r = v / 10000; uint8_t g = (v - r * 10000) / 100; uint8_t b = v % 100; - rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b); + rgb_led::write(sensor.gpio(), 2 * r, 2 * g, 2 * b); LOG_DEBUG("RGB set to %d, %d, %d", r, g, b); } else if (sensor.type() == AnalogType::DIGITAL_OUT) { LOG_DEBUG("Digital Write on GPIO %02d", sensor.gpio()); @@ -405,8 +405,8 @@ void AnalogSensor::measure() { sensor.sum_ = (sensor.sum_ * 15 + a * 16) / 16; sensor.analog_ = sensor.sum_ / 16; } - if (sensor.analog_ > 0 && sensor.analog_ < 3300 && (sensor.last_reading_ + 1 < sensor.analog_ || sensor.last_reading_ > sensor.analog_ + 1)) { - sensor.set_value(sensor.offset() + 1 / (1 / T25 + log((double)sensor.analog_ / (3300 - sensor.analog_) * (Rt / R0)) / Beta) + if (sensor.analog_ > 0 && sensor.analog_ < NTC_VREF_MV && (sensor.last_reading_ + 1 < sensor.analog_ || sensor.last_reading_ > sensor.analog_ + 1)) { + sensor.set_value(sensor.offset() + 1 / (1 / T25 + log((double)sensor.analog_ / (NTC_VREF_MV - sensor.analog_) * (Rt / R0)) / Beta) - T0); // Temperature in Celsius sensor.last_reading_ = sensor.analog_; sensorreads_++; @@ -424,7 +424,7 @@ void AnalogSensor::measure() { edgecnt[index] = 0; portEXIT_CRITICAL_ISR(&mux); sensor.set_value(sensor.factor() * 1000000.0 / t); - } else if (micros() - edge[index] > 10000000ul && sensor.value() > 0) { + } else if (micros() - edge[index] > FREQ_ZERO_TIMEOUT_US && sensor.value() > 0) { sensor.set_value(0); } if (sensor.value() != oldval) { @@ -999,7 +999,7 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) { uint8_t r = v / 10000; uint8_t g = (v - r * 10000) / 100; uint8_t b = v % 100; - rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b); + rgb_led::write(sensor.gpio(), 2 * r, 2 * g, 2 * b); LOG_DEBUG("RGB set to %d, %d, %d", r, g, b); } else if (sensor.type() == AnalogType::PULSE) { uint8_t v = val; diff --git a/src/core/led.cpp b/src/core/led.cpp index 958ac9ac3..f6c708e8c 100644 --- a/src/core/led.cpp +++ b/src/core/led.cpp @@ -19,8 +19,117 @@ #include "led.h" #include "emsesp.h" +#ifndef EMSESP_STANDALONE +#include +#endif + namespace emsesp { +namespace rgb_led { + +#ifndef EMSESP_STANDALONE + +namespace { + +// durations in 100 ns ticks +constexpr uint16_t T0H = 3; // 300 ns +constexpr uint16_t T0L = 9; // 900 ns +constexpr uint16_t T1H = 8; // 800 ns +constexpr uint16_t T1L = 6; // 600 ns + +constexpr uint32_t RMT_FREQ_HZ = 10000000; // 10 MHz +constexpr uint32_t RESET_US = 300; +constexpr uint8_t NO_PIN = 0xFF; +constexpr uint8_t MAX_PINS = 2; // status LED + an RGB analog sensor; the ESP32-C3 only has 2 RMT TX channels + +struct Channel { + uint8_t pin = NO_PIN; + uint32_t last_tx_us = 0; +}; + +Channel channels[MAX_PINS]; + +Channel * init(uint8_t pin) { + Channel * free_slot = nullptr; + for (auto & ch : channels) { + if (ch.pin == pin) { + return &ch; + } + if (!free_slot && ch.pin == NO_PIN) { + free_slot = &ch; + } + } + + if (!free_slot) { + // all slots in use: release the oldest one + free_slot = &channels[0]; + end(free_slot->pin); + } + + if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, RMT_FREQ_HZ)) { + return nullptr; + } + free_slot->pin = pin; + free_slot->last_tx_us = micros() - RESET_US; // no wait on first write + return free_slot; +} + +} // namespace + +bool write(uint8_t pin, uint8_t red, uint8_t green, uint8_t blue, Order order) { + Channel * ch = init(pin); + if (!ch) { + return false; + } + + // enforce the latch/reset gap since the previous frame (V6 needs > 280 us) + const uint32_t elapsed = micros() - ch->last_tx_us; + if (elapsed < RESET_US) { + delayMicroseconds(RESET_US - elapsed); + } + + const uint8_t bytes[3] = {order == Order::GRB ? green : red, order == Order::GRB ? red : green, blue}; + + rmt_data_t frame[24]; + for (uint8_t i = 0; i < 24; i++) { + const bool one = bytes[i >> 3] & (0x80 >> (i & 7)); + frame[i].level0 = 1; + frame[i].duration0 = one ? T1H : T0H; + frame[i].level1 = 0; + frame[i].duration1 = one ? T1L : T0L; + } + + const bool ok = rmtWrite(pin, frame, 24, RMT_WAIT_FOR_EVER); + ch->last_tx_us = micros(); + return ok; +} + +void end(uint8_t pin) { + for (auto & ch : channels) { + if (ch.pin == pin && pin != NO_PIN) { + rmtDeinit(pin); + ch.pin = NO_PIN; + } + } +} + +#else + +bool write(uint8_t, uint8_t, uint8_t, uint8_t, Order) { + return true; +} + +void end(uint8_t) { +} + +#endif + +bool off(uint8_t pin) { + return write(pin, 0, 0, 0); +} + +} // namespace rgb_led + uuid::log::Logger LED::logger_{F_(led), uuid::log::Facility::KERN}; // initialise the LED, fetching the settings from the WebSettingsService @@ -189,7 +298,7 @@ void LED::set_led(Color color) { const uint8_t blue = rgb_table[idx][2]; if (led_type_) { - rgbLedWrite(led_gpio_, red, green, blue); + rgb_led::write(led_gpio_, red, green, blue); } else { digitalWrite(led_gpio_, (red == 0 && green == 0 && blue == 0) || color == Color::OFF ? !LED_ON : LED_ON); } diff --git a/src/core/led.h b/src/core/led.h index 3c4502d26..f12f8bba2 100644 --- a/src/core/led.h +++ b/src/core/led.h @@ -24,6 +24,30 @@ namespace emsesp { +// WS2812B driver replacing arduino-esp32's rgbLedWrite()/neopixelWrite(), which hard-code +// T0H = 400 ns, T1L = 400 ns and have no reset gap. The WS2812B-2020-V6 needs T0H 220-380 ns, +// T1L 580-1000 ns and a reset > 280 us, otherwise it misreads 0-bits as 1-bits (washed-out white). +// Timings used (10 MHz RMT clock, 1 tick = 100 ns) fit both the original WS2812B and the V6: +// +// sent old WS2812B WS2812B-2020-V6 +// T0H 300 ns 250-550 ns 220-380 ns +// T0L 900 ns 700-1000 ns 580-1000 ns +// T1H 800 ns 650-950 ns 580-1000 ns +// T1L 600 ns 300-600 ns 580-1000 ns +// Reset >300 us >50 us >280 us +// +// Do not mix with rgbLedWrite()/neopixelWrite() on the same pin, as both claim the pin's RMT channel. +namespace rgb_led { + +enum class Order : uint8_t { GRB, RGB }; // WS2812B is GRB + +// Blocks ~30 us for the transmit, plus up to the reset gap if called again right after the previous update +bool write(uint8_t pin, uint8_t red, uint8_t green, uint8_t blue, Order order = Order::GRB); +bool off(uint8_t pin); +void end(uint8_t pin); // release the pin's RMT channel, e.g. before reusing the pin for something else + +} // namespace rgb_led + class LED { public: enum Color : uint8_t { diff --git a/src/emsesp_version.h b/src/emsesp_version.h index 7d790a329..dd02903f5 100644 --- a/src/emsesp_version.h +++ b/src/emsesp_version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.9.0-dev.13" +#define EMSESP_APP_VERSION "3.9.0-dev.14" From 100edcefe2f7220ed193bcbf9a510aeedaf58d77 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 24 Sep 2026 17:21:41 +0200 Subject: [PATCH 3/5] optimize LED and fix some edge cases --- src/core/led.cpp | 133 +++++++++++++++----------------------- src/core/led.h | 32 ++++----- src/core/system.cpp | 25 ++++--- test/test_api/api_test.sh | 95 ++++++++++++++++++--------- 4 files changed, 150 insertions(+), 135 deletions(-) diff --git a/src/core/led.cpp b/src/core/led.cpp index f6c708e8c..e05756215 100644 --- a/src/core/led.cpp +++ b/src/core/led.cpp @@ -37,6 +37,10 @@ constexpr uint16_t T0L = 9; // 900 ns constexpr uint16_t T1H = 8; // 800 ns constexpr uint16_t T1L = 6; // 600 ns +// rmt_data_t.val layout: duration0:15, level0:1, duration1:15, level1:1 (high then low) +constexpr uint32_t SYMBOL_0 = T0H | (1UL << 15) | (static_cast(T0L) << 16); +constexpr uint32_t SYMBOL_1 = T1H | (1UL << 15) | (static_cast(T1L) << 16); + constexpr uint32_t RMT_FREQ_HZ = 10000000; // 10 MHz constexpr uint32_t RESET_US = 300; constexpr uint8_t NO_PIN = 0xFF; @@ -61,8 +65,14 @@ Channel * init(uint8_t pin) { } if (!free_slot) { - // all slots in use: release the oldest one - free_slot = &channels[0]; + // all slots in use: release the least recently written one + const uint32_t now = micros(); + free_slot = &channels[0]; + for (auto & ch : channels) { + if (now - ch.last_tx_us > now - free_slot->last_tx_us) { + free_slot = &ch; + } + } end(free_slot->pin); } @@ -92,11 +102,7 @@ bool write(uint8_t pin, uint8_t red, uint8_t green, uint8_t blue, Order order) { rmt_data_t frame[24]; for (uint8_t i = 0; i < 24; i++) { - const bool one = bytes[i >> 3] & (0x80 >> (i & 7)); - frame[i].level0 = 1; - frame[i].duration0 = one ? T1H : T0H; - frame[i].level1 = 0; - frame[i].duration1 = one ? T1L : T0L; + frame[i].val = (bytes[i >> 3] & (0x80 >> (i & 7))) ? SYMBOL_1 : SYMBOL_0; } const bool ok = rmtWrite(pin, frame, 24, RMT_WAIT_FOR_EVER); @@ -124,10 +130,6 @@ void end(uint8_t) { #endif -bool off(uint8_t pin) { - return write(pin, 0, 0, 0); -} - } // namespace rgb_led uuid::log::Logger LED::logger_{F_(led), uuid::log::Facility::KERN}; @@ -135,12 +137,21 @@ uuid::log::Logger LED::logger_{F_(led), uuid::log::Facility::KERN}; // initialise the LED, fetching the settings from the WebSettingsService // set the LED to on or off when in normal operating mode void LED::init() { + // settings may have changed: turn off and release the previous pin + if (led_gpio_) { + set_led(Color::OFF); + if (led_type_) { + rgb_led::end(led_gpio_); + } + } + // copy the application settings EMSESP::webSettingsService.read([&](WebSettings & settings) { led_gpio_ = settings.led_gpio; led_type_ = settings.led_type; hide_led_ = settings.hide_led; }); + last_color_ = COLOR_UNSET; if (!led_gpio_) { // 0 means disabled LOG_INFO("LED disabled"); @@ -183,10 +194,6 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) { // check the system health. // Set the sequence accordingly, only if the healthcheck is not 0 and has changed if (healthcheck != previous_healthcheck_) { - color_steps_[0] = Color::OFF; - color_steps_[1] = Color::OFF; - color_steps_[2] = Color::OFF; - // if the healthcheck is 0, i.e. system is healthy, reset the LED if (healthcheck == 0) { reset_led(); @@ -194,25 +201,12 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) { // 1 flash (blue) is the EMS bus is not connected // 2 flashes (red, red) if the network (wifi or ethernet) is not connected // 3 flashes (red, red, blue) is both the bus and the network are not connected - bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK; - bool no_bus = (healthcheck & System::HEALTHCHECK_NO_BUS) == System::HEALTHCHECK_NO_BUS; + const bool no_network = (healthcheck & System::HEALTHCHECK_NO_NETWORK) == System::HEALTHCHECK_NO_NETWORK; + const bool no_bus = (healthcheck & System::HEALTHCHECK_NO_BUS) == System::HEALTHCHECK_NO_BUS; - // set step 1 - if (no_network) { - color_steps_[0] = Color::RED; // red, no network - } else if (no_bus) { - color_steps_[0] = Color::BLUE; // blue, no bus - } - - // set step 2 - if (no_network) { - color_steps_[1] = Color::RED; // red, no network - } - - // set step 3 - if (no_network && no_bus) { - color_steps_[2] = Color::BLUE; // blue, no network and no bus - } + color_steps_[0] = no_network ? Color::RED : (no_bus ? Color::BLUE : Color::OFF); + color_steps_[1] = no_network ? Color::RED : Color::OFF; + color_steps_[2] = (no_network && no_bus) ? Color::BLUE : Color::OFF; } previous_healthcheck_ = healthcheck; // must be set after reset_led(), which invalidates it @@ -235,9 +229,7 @@ bool LED::loop(uint8_t healthcheck, bool button_busy) { void LED::reset_led() { is_user_led_blink_ = false; set_led(hide_led_ ? Color::OFF : Color::GREEN); // Green - color_steps_[0] = Color::OFF; - color_steps_[1] = Color::OFF; - color_steps_[2] = Color::OFF; + color_steps_[0] = color_steps_[1] = color_steps_[2] = Color::OFF; // invalidate the last health state so the sequence is rebuilt on the next check, // needed because the color steps above have been cleared @@ -272,9 +264,10 @@ void LED::led_fast_flash() { // set LED on/off or RGB color // ignores whether the LED is hidden or not (if hide_led_ is set) void LED::set_led(Color color) { - if (!led_gpio_) { + if (!led_gpio_ || color == last_color_) { return; } + last_color_ = color; // RGB lookup table indexed by Color enum (must match enum order in led.h) static constexpr uint8_t B = RGB_LED_BRIGHTNESS; @@ -290,28 +283,26 @@ void LED::set_led(Color color) { {0, B, B}, // CYAN {H, 0, H} // PINK }; + static_assert(sizeof(rgb_table) / sizeof(rgb_table[0]) == Color::PINK + 1, "rgb_table must match the Color enum"); - const uint8_t color_idx = static_cast(color); - const uint8_t idx = (color_idx < sizeof(rgb_table) / sizeof(rgb_table[0])) ? color_idx : static_cast(Color::OFF); - const uint8_t red = rgb_table[idx][0]; - const uint8_t green = rgb_table[idx][1]; - const uint8_t blue = rgb_table[idx][2]; + const uint8_t * rgb = rgb_table[color]; if (led_type_) { - rgb_led::write(led_gpio_, red, green, blue); + rgb_led::write(led_gpio_, rgb[0], rgb[1], rgb[2]); } else { - digitalWrite(led_gpio_, (red == 0 && green == 0 && blue == 0) || color == Color::OFF ? !LED_ON : LED_ON); + digitalWrite(led_gpio_, color == Color::OFF ? !LED_ON : LED_ON); } } // set LED custom routine // For example: /api/system/led?data=red:blink1 // For older non-RGB models, the colour would default to just being on. -bool LED::set_custom_led_routine(std::string color, std::string pattern) { +bool LED::set_custom_led_routine(const std::string & color, const std::string & pattern) { static constexpr struct { const char * name; Color value; } color_map[] = { + {"", Color::OFF}, {"off", Color::OFF}, {"on", Color::ON}, {"white", Color::ON}, @@ -337,35 +328,33 @@ bool LED::set_custom_led_routine(std::string color, std::string pattern) { return false; } - // reset the color steps - color_steps_[0] = Color::OFF; - color_steps_[1] = Color::OFF; - color_steps_[2] = Color::OFF; + // build the steps locally so an unrecognized pattern leaves the current sequence untouched + Color steps[3] = {Color::OFF, Color::OFF, Color::OFF}; // blink patterns if (pattern == "blink1") { - color_steps_[0] = color_type; + steps[0] = color_type; } else if (pattern == "blink2") { - color_steps_[0] = color_type; - color_steps_[1] = color_type; + steps[0] = steps[1] = color_type; } else if (pattern == "blink3") { - color_steps_[0] = color_type; - color_steps_[1] = color_type; - color_steps_[2] = color_type; + steps[0] = steps[1] = steps[2] = color_type; // special patterns, ignores the user color } else if (pattern == "rgb") { - color_steps_[0] = Color::RED; - color_steps_[1] = Color::GREEN; - color_steps_[2] = Color::BLUE; + steps[0] = Color::RED; + steps[1] = Color::GREEN; + steps[2] = Color::BLUE; } else if (pattern == "cpc") { - color_steps_[0] = Color::CYAN; - color_steps_[1] = Color::PINK; - color_steps_[2] = Color::CYAN; + steps[0] = Color::CYAN; + steps[1] = Color::PINK; + steps[2] = Color::CYAN; } else { return false; // pattern not recognized } + color_steps_[0] = steps[0]; + color_steps_[1] = steps[1]; + color_steps_[2] = steps[2]; is_user_led_blink_ = true; // user routine is active // when this is called we want the sequence_led to restart immediately and skip the long pause @@ -395,7 +384,7 @@ void LED::sequence_led() { if (++led_flash_step_ == 8) { // finished first iteration, reset the whole sequence, turn off LED - led_long_timer_ = uuid::get_uptime(); + led_long_timer_ = current_time; led_flash_step_ = 0; set_led(Color::OFF); // turn off the LED @@ -407,24 +396,8 @@ void LED::sequence_led() { return; } - if (led_flash_step_ % 2) { - // handle the three step events (on odd numbers 3,5,7 etc). see if we need to set a LED color - switch (led_flash_step_) { - case 3: // first flash - set_led(color_steps_[0]); - break; - case 5: // second flash - set_led(color_steps_[1]); - break; - case 7: // third flash - set_led(color_steps_[2]); - break; - default: - break; - } - } else { - set_led(Color::OFF); // turn off on even number count, to make it flash - } + // odd steps 3, 5 and 7 show the three configured colors, even steps turn the LED off to make it flash + set_led(led_flash_step_ % 2 ? color_steps_[(led_flash_step_ - 3) / 2] : Color::OFF); } } diff --git a/src/core/led.h b/src/core/led.h index f12f8bba2..ba9a2d6ed 100644 --- a/src/core/led.h +++ b/src/core/led.h @@ -43,7 +43,6 @@ enum class Order : uint8_t { GRB, RGB }; // WS2812B is GRB // Blocks ~30 us for the transmit, plus up to the reset gap if called again right after the previous update bool write(uint8_t pin, uint8_t red, uint8_t green, uint8_t blue, Order order = Order::GRB); -bool off(uint8_t pin); void end(uint8_t pin); // release the pin's RMT channel, e.g. before reusing the pin for something else } // namespace rgb_led @@ -66,7 +65,7 @@ class LED { bool loop(uint8_t healthcheck, bool button_busy); void start_led_fast_flash(uint8_t duration); // duration in seconds - bool set_custom_led_routine(std::string color, std::string pattern); + bool set_custom_led_routine(const std::string & color, const std::string & pattern); private: static uuid::log::Logger logger_; @@ -83,28 +82,31 @@ class LED { static constexpr uint32_t LED_FLASH_INTERVAL_MS = 100; // LED toggle period during factory-reset flash static constexpr uint8_t RGB_LED_BRIGHTNESS = 20; // 255 is max brightness static constexpr uint8_t LED_ON = HIGH; // LED on + static constexpr Color COLOR_UNSET = static_cast(0xFF); // forces the next set_led() to write + + // led_fast_flash() state + uint32_t led_flash_start_time_ = 0; + uint32_t led_flash_duration_ = 0; + uint32_t last_toggle_time_ = 0; + + // sequence_led() state + uint32_t led_long_timer_ = 1; // 1 will kick it off immediately + uint32_t led_short_timer_ = 0; // local copies of the application settings uint8_t led_gpio_ = 0; uint8_t led_type_ = 0; bool hide_led_ = false; - bool led_fast_flash_timer_ = false; - uint32_t led_flash_start_time_ = 0; - uint32_t led_flash_duration_ = 0; + bool led_fast_flash_timer_ = false; + bool led_flash_state_ = false; - // led_flash() state - bool led_flash_state_ = false; - uint32_t last_toggle_time_ = 0; + bool last_button_busy_ = false; + uint8_t led_flash_step_ = 0; // 0 means we're not in the short flash timer - // sequence_led() state - bool last_button_busy_ = false; - uint32_t led_long_timer_ = 1; // 1 will kick it off immediately - uint32_t led_short_timer_ = 0; - uint8_t led_flash_step_ = 0; // 0 means we're not in the short flash timer - - // set_led_routine() state + // set_custom_led_routine() and healthcheck state Color color_steps_[3] = {Color::OFF, Color::OFF, Color::OFF}; + Color last_color_ = COLOR_UNSET; // last color written, to skip redundant writes // if true, the user has requested a custom LED blink, this always has preference over the button or showing the system health bool is_user_led_blink_ = false; diff --git a/src/core/system.cpp b/src/core/system.cpp index a2860bfc3..0283bb220 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2924,11 +2924,13 @@ bool System::load_board_profile(std::vector & data, const std::string & // https://github.com/emsesp/EMS-ESP32/issues/3063 // /api//system/led command that takes an argument in the form [color]:[pattern] // color is red, green, blue, yellow, white +// color is optional // pattern is // blink1 for 1 time // blink2 for 2 times // blink3 for 3 times // rgb for RGB +// cpc for CPC // For example: /api/system/led?data=red:blink1 // For older non-RGB models, the colour would default to just being on. bool System::command_led(const char * value, const int8_t) { @@ -2937,16 +2939,19 @@ bool System::command_led(const char * value, const int8_t) { } std::string arg = value; - if (arg.find(':') == std::string::npos) { - LOG_ERROR("LED command must be in the form [color]:[pattern]"); - return false; // not in the form [color]:[pattern] + std::string color; + std::string pattern; + auto sep = arg.find(':'); + if (sep == std::string::npos) { + pattern = arg; // no color given, only a pattern + } else { + color = arg.substr(0, sep); + pattern = arg.substr(sep + 1); } - std::string color = arg.substr(0, arg.find(':')); - std::string pattern = arg.substr(arg.find(':') + 1); // set and validate the color and pattern if (!EMSESP::led_.set_custom_led_routine(color, pattern)) { - LOG_ERROR("Invalid color or pattern."); + LOG_ERROR("Invalid color or pattern"); return false; } @@ -3207,7 +3212,7 @@ bool System::uploadFirmwareURL(const char * url) { } basic_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S * 1000); // socket-level read timeout, in ms ssl_client.setTimeout(FIRMWARE_UPLOAD_READ_TIMEOUT_S); // Stream::readBytes timeout used by Update - ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP + ssl_client.setClient(&basic_client, is_https); // enableSSL = false for plain HTTP const uint16_t port = is_https ? 443 : 80; String url_remain = saved_url.substring(scheme_len); @@ -3355,9 +3360,9 @@ bool System::uploadFirmwareURL(const char * url) { // explicit chunked read loop instead of Update.writeStream(): uint8_t buf[FIRMWARE_UPLOAD_CHUNK_SIZE]; - size_t total_read = 0; - bool magic_ok = false; - int last_pct = -1; + size_t total_read = 0; + bool magic_ok = false; + int last_pct = -1; while (total_read < (size_t)firmware_size) { // a cancel is signalled by the WebUI dropping the status below UPLOADING (back to NORMAL) diff --git a/test/test_api/api_test.sh b/test/test_api/api_test.sh index 839de5b04..4040f78cc 100755 --- a/test/test_api/api_test.sh +++ b/test/test_api/api_test.sh @@ -1,53 +1,88 @@ -#!/bin/sh - +#!/usr/bin/env bash # # Command line test for the API # -emsesp_url="http://ems-esp.local" +emsesp_url="http://ems-espT.local" # get the token from the Security page. This is the token for the admin user, unless changed it'll always be the same emsesp_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWV9.2bHpWya2C7Q12WjNUBD6_7N3RCD7CMl-EGhyQVzFdDg" # This example will get the system info, via a GET request -curl -X GET ${emsesp_url}/api/system/info -echo "\n" +test0() { + curl -X GET ${emsesp_url}/api/system/info +} # This example will execute a read command on product ID 8 and type ID 1 -curl -X POST \ - -H "Authorization: Bearer ${emsesp_token}" \ - -H "Content-Type: application/json" \ - -d '{"data":"8 1"}' \ - ${emsesp_url}/api/system/read - -echo "\n" +test1() { + curl -X POST \ + -H "Authorization: Bearer ${emsesp_token}" \ + -H "Content-Type: application/json" \ + -d '{"data":"8 1"}' \ + ${emsesp_url}/api/system/read +} # This example will use the message command to get the locale (en) -curl -X POST \ - -H "Authorization: Bearer ${emsesp_token}" \ - -H "Content-Type: application/json" \ - -d '{"value":"system/settings/locale"}' \ - ${emsesp_url}/api/system/message - -echo "\n" +test2() { + curl -X POST \ + -H "Authorization: Bearer ${emsesp_token}" \ + -H "Content-Type: application/json" \ + -d '{"value":"system/settings/locale"}' \ + ${emsesp_url}/api/system/message +} # This example will export all values to a json file, including custom entities, sensors and schedules -curl -X POST \ - -H "Authorization: Bearer ${emsesp_token}" \ - -H "Content-Type: application/json" \ - -d '{"action":"export", "param":"allvalues"}' \ - ${emsesp_url}/rest/action - -echo "\n" +test3() { + curl -X POST \ + -H "Authorization: Bearer ${emsesp_token}" \ + -H "Content-Type: application/json" \ + -d '{"action":"export", "param":"allvalues"}' \ + ${emsesp_url}/rest/action +} # Get all versions -curl -X POST \ +test4() { + curl -X POST \ -H "Authorization: Bearer ${emsesp_token}" \ -H "Content-Type: application/json" \ - -d '{"action":"getVersions"}' \ - ${emsesp_url}/rest/action + -d '{"action":"getVersions"}' \ + ${emsesp_url}/rest/action +} + +# led tests +test5() { + curl -X POST \ + -H "Authorization: Bearer ${emsesp_token}" \ + -H "Content-Type: application/json" \ + -d '{"data":"red:blink1"}' \ + ${emsesp_url}/api/system/led +} + +test6() { + curl -X POST \ + -H "Authorization: Bearer ${emsesp_token}" \ + -H "Content-Type: application/json" \ + -d '{"data":"rgb"}' \ + ${emsesp_url}/api/system/led +} + +# Create a list of test function names to run +tests_to_run=( + # test0 + # test1 + # test2 + # test3 + # test4 + test5 + test6 +) + +for test_func in "${tests_to_run[@]}"; do + echo "Running $test_func..." + $test_func + echo "" +done -echo "\n" # This example is how to call a service in Home Assistant via the API # Which can be added to an EMS-EPS schedule From 09e8ef540373cababf62e119e52edbf6a879c479 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 24 Sep 2026 17:29:06 +0200 Subject: [PATCH 4/5] pkg update --- interface/package.json | 2 +- interface/pnpm-lock.yaml | 174 +++++++++++++++++++-------------------- mock-api/package.json | 2 +- mock-api/pnpm-lock.yaml | 16 ++-- project-words.txt | 1 + 5 files changed, 98 insertions(+), 97 deletions(-) diff --git a/interface/package.json b/interface/package.json index 6cc252a62..967f99404 100644 --- a/interface/package.json +++ b/interface/package.json @@ -50,7 +50,7 @@ "eslint-config-prettier": "^10.1.8", "etag": "^1.8.1", "mime-types": "^3.0.2", - "prettier": "^3.9.8", + "prettier": "^3.9.9", "rollup-plugin-visualizer": "^7.1.1", "terser": "^5.51.2", "typescript": "^6.0.3", diff --git a/interface/pnpm-lock.yaml b/interface/pnpm-lock.yaml index 3c2e56a47..acd7c818d 100644 --- a/interface/pnpm-lock.yaml +++ b/interface/pnpm-lock.yaml @@ -217,7 +217,7 @@ importers: version: 2.10.6(@babel/core@7.29.7(supports-color@10.2.2))(preact@10.29.8)(supports-color@10.2.2)(vite@8.3.0(@types/node@26.6.2)(terser@5.51.2)) '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 - version: 6.0.2(prettier@3.9.8)(supports-color@10.2.2) + version: 6.0.2(prettier@3.9.9)(supports-color@10.2.2) '@types/node': specifier: ^26.6.2 version: 26.6.2 @@ -243,11 +243,11 @@ importers: specifier: ^3.0.2 version: 3.0.2 prettier: - specifier: ^3.9.8 - version: 3.9.8 + specifier: ^3.9.9 + version: 3.9.9 rollup-plugin-visualizer: specifier: ^7.1.1 - version: 7.1.1(rolldown@1.2.9) + version: 7.1.1(rolldown@1.2.10) terser: specifier: ^5.51.2 version: 5.51.2 @@ -627,8 +627,8 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@oxc-project/types@0.150.0': - resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==} + '@oxc-project/types@0.151.0': + resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==} '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -661,98 +661,98 @@ packages: '@remix-run/route-pattern@0.22.1': resolution: {integrity: sha512-czdGJWh09Lapvcqt7Vb09KlrU2PhRJ8xQaI+AItTuD9aDJ5MAgxnS38lnys6Ll+6RJEqPiaUqTwIQhVLwFvv+w==} - '@rolldown/binding-android-arm-eabi@1.2.9': - resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==} + '@rolldown/binding-android-arm-eabi@1.2.10': + resolution: {integrity: sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@rolldown/binding-android-arm64@1.2.9': - resolution: {integrity: sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==} + '@rolldown/binding-android-arm64@1.2.10': + resolution: {integrity: sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.2.9': - resolution: {integrity: sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==} + '@rolldown/binding-darwin-arm64@1.2.10': + resolution: {integrity: sha512-UbEfXq/AqGNgRTV3ik+X/iR6mUxu2QdYAadwRxJWquUGnW6gDqdP1FtLtFXRow7RJx0ssRwi80XAPr4r+4DtsA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.2.9': - resolution: {integrity: sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==} + '@rolldown/binding-darwin-x64@1.2.10': + resolution: {integrity: sha512-7f5h17q5KZVx/ji1vb8OTq31ch1O2I7K8NPIr44GkyWTApXMIsmhWqZfgpOH10xeauqghDAvGlZktasCkcF6Eg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.2.9': - resolution: {integrity: sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==} + '@rolldown/binding-freebsd-x64@1.2.10': + resolution: {integrity: sha512-ynOk/eEYhC6ZB2xCGvKrEOwE58oBy9LnrAqtkrDF9Fz1VTaNdGZTsV0VarJdhPwb+sOJTGjCLwcuyRJZ1dnMcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': - resolution: {integrity: sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.10': + resolution: {integrity: sha512-ERrAs185meZZhGan7a4l3RiiJK1ArSDlHdST++uvSxe+FDbR4TwUPahT/cbZJvaG6fIpDpF78surN+tX708Y4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.2.9': - resolution: {integrity: sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==} + '@rolldown/binding-linux-arm64-gnu@1.2.10': + resolution: {integrity: sha512-KN7OHKD0J3jy1UzBwZWPxpwhODf9IARUIJcrH+yLYKOcmegZ8luEUM38lDP1bDVj40yP6PsSzCqOJF76vljFnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.2.9': - resolution: {integrity: sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==} + '@rolldown/binding-linux-arm64-musl@1.2.10': + resolution: {integrity: sha512-8l9wP8O+wa8zD6iw6egSfzVtu7oZVfH3hlUsMM4MwbLMhxleqeoXbZzjddyK3YyNlwLhqznq3tF7PkNJ8T/V2w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.2.9': - resolution: {integrity: sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==} + '@rolldown/binding-linux-ppc64-gnu@1.2.10': + resolution: {integrity: sha512-SeXNKeQzA5kLhz/J0CH6ZP0/HJ3v1xm/0YbiYpE0kK7emfRC2OIGGIaE14xzkISEGv2aYuUSpiLiU5Gbq+OI0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.2.9': - resolution: {integrity: sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.10': + resolution: {integrity: sha512-mtht0nR+y8/hart4175Ll15w7lY8dg7CtQ+j2FDNTsDRspOWTK/2V3l0aj9sIj7XmvqxT8Yli/wq22e7feTTWg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.2.9': - resolution: {integrity: sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==} + '@rolldown/binding-linux-x64-gnu@1.2.10': + resolution: {integrity: sha512-FSM94nGd55NYo48usCyM/nHfUKRnqc9+b0vJNuKV0oCCpIp/OGims7rO1Nv/DkFkt0S/s2rxsJ2kkS8J3HcpeA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.2.9': - resolution: {integrity: sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==} + '@rolldown/binding-linux-x64-musl@1.2.10': + resolution: {integrity: sha512-C3YxNB16myRLs7o+B+6PnQ6jBsdIS4+AE4Ah8glVGhDpEv9AOvxhZ/1duAb4B0UGczEK/lBbccksd8VI+p6zfw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.2.9': - resolution: {integrity: sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==} + '@rolldown/binding-openharmony-arm64@1.2.10': + resolution: {integrity: sha512-571TlE/F1eeTjjdjYAMMMPs1Mfv3MtX6s3+ZKVU6HiUjZ5Njc6c/qzNy/8K3zALTZnaw3JQVYrHxvNfjm43KAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-win32-arm64-msvc@1.2.9': - resolution: {integrity: sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==} + '@rolldown/binding-win32-arm64-msvc@1.2.10': + resolution: {integrity: sha512-QXW+ZWaiqs2c7Fi++D/SsW07LTPcUrncxcskJGfGNBoaLik1IU6fJymz4HsqwEO0u5Iq11yTO0B/mc4cPk7jrQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.9': - resolution: {integrity: sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==} + '@rolldown/binding-win32-x64-msvc@1.2.10': + resolution: {integrity: sha512-5FQFGgah17YeMtG1Yd5a+rMxQpTksyNXxRtKz06FVTaQw3RKYUJQbUoKk0/5jrXBpDo+7makNP7UHA2LQyH64A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1338,8 +1338,8 @@ packages: duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - electron-to-chromium@1.5.434: - resolution: {integrity: sha512-7EeFW9OLf1NN9NKQP9xdOE/XKqjxv7JDPP6AEAkHli11+Fae1OaLoNuAf13hQv1PPNtuES0pLQSPk2+fS3s8ww==} + electron-to-chromium@1.5.438: + resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==} emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} @@ -2318,8 +2318,8 @@ packages: node-html-parser@6.1.13: resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} - node-releases@2.0.56: - resolution: {integrity: sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==} + node-releases@2.0.57: + resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==} engines: {node: '>=18'} normalize-package-data@2.5.0: @@ -2555,8 +2555,8 @@ packages: resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} engines: {node: '>=4'} - prettier@3.9.8: - resolution: {integrity: sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==} + prettier@3.9.9: + resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} engines: {node: '>=14'} hasBin: true @@ -2686,8 +2686,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rolldown@1.2.9: - resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==} + rolldown@1.2.10: + resolution: {integrity: sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3591,7 +3591,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.3 - '@oxc-project/types@0.150.0': {} + '@oxc-project/types@0.151.0': {} '@popperjs/core@2.11.8': {} @@ -3638,49 +3638,49 @@ snapshots: '@remix-run/route-pattern@0.22.1': {} - '@rolldown/binding-android-arm-eabi@1.2.9': + '@rolldown/binding-android-arm-eabi@1.2.10': optional: true - '@rolldown/binding-android-arm64@1.2.9': + '@rolldown/binding-android-arm64@1.2.10': optional: true - '@rolldown/binding-darwin-arm64@1.2.9': + '@rolldown/binding-darwin-arm64@1.2.10': optional: true - '@rolldown/binding-darwin-x64@1.2.9': + '@rolldown/binding-darwin-x64@1.2.10': optional: true - '@rolldown/binding-freebsd-x64@1.2.9': + '@rolldown/binding-freebsd-x64@1.2.10': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.2.9': + '@rolldown/binding-linux-arm-gnueabihf@1.2.10': optional: true - '@rolldown/binding-linux-arm64-gnu@1.2.9': + '@rolldown/binding-linux-arm64-gnu@1.2.10': optional: true - '@rolldown/binding-linux-arm64-musl@1.2.9': + '@rolldown/binding-linux-arm64-musl@1.2.10': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.2.9': + '@rolldown/binding-linux-ppc64-gnu@1.2.10': optional: true - '@rolldown/binding-linux-s390x-gnu@1.2.9': + '@rolldown/binding-linux-s390x-gnu@1.2.10': optional: true - '@rolldown/binding-linux-x64-gnu@1.2.9': + '@rolldown/binding-linux-x64-gnu@1.2.10': optional: true - '@rolldown/binding-linux-x64-musl@1.2.9': + '@rolldown/binding-linux-x64-musl@1.2.10': optional: true - '@rolldown/binding-openharmony-arm64@1.2.9': + '@rolldown/binding-openharmony-arm64@1.2.10': optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.9': + '@rolldown/binding-win32-arm64-msvc@1.2.10': optional: true - '@rolldown/binding-win32-x64-msvc@1.2.9': + '@rolldown/binding-win32-x64-msvc@1.2.10': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -3707,7 +3707,7 @@ snapshots: react-virtualized-auto-sizer: 1.0.26(react-dom@19.3.0(react@19.3.0))(react@19.3.0) react-window: 1.8.11(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)(supports-color@10.2.2)': + '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.9)(supports-color@10.2.2)': dependencies: '@babel/generator': 7.29.8 '@babel/parser': 7.29.9 @@ -3717,7 +3717,7 @@ snapshots: lodash-es: 4.18.1 minimatch: 9.0.9 parse-imports-exports: 0.2.4 - prettier: 3.9.8 + prettier: 3.9.9 transitivePeerDependencies: - supports-color @@ -4009,8 +4009,8 @@ snapshots: dependencies: baseline-browser-mapping: 2.11.25 caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.434 - node-releases: 2.0.56 + electron-to-chromium: 1.5.438 + node-releases: 2.0.57 update-browserslist-db: 1.3.3(browserslist@4.29.0) buffer-alloc-unsafe@1.1.0: {} @@ -4374,7 +4374,7 @@ snapshots: duplexer3@0.1.5: {} - electron-to-chromium@1.5.434: {} + electron-to-chromium@1.5.438: {} emoji-regex@10.6.0: {} @@ -5311,7 +5311,7 @@ snapshots: css-select: 5.2.2 he: 1.2.0 - node-releases@2.0.56: {} + node-releases@2.0.57: {} normalize-package-data@2.5.0: dependencies: @@ -5515,7 +5515,7 @@ snapshots: prepend-http@2.0.0: {} - prettier@3.9.8: {} + prettier@3.9.9: {} process-nextick-args@2.0.1: {} @@ -5645,35 +5645,35 @@ snapshots: dependencies: glob: 7.2.3 - rolldown@1.2.9: + rolldown@1.2.10: dependencies: - '@oxc-project/types': 0.150.0 + '@oxc-project/types': 0.151.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm-eabi': 1.2.9 - '@rolldown/binding-android-arm64': 1.2.9 - '@rolldown/binding-darwin-arm64': 1.2.9 - '@rolldown/binding-darwin-x64': 1.2.9 - '@rolldown/binding-freebsd-x64': 1.2.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.9 - '@rolldown/binding-linux-arm64-gnu': 1.2.9 - '@rolldown/binding-linux-arm64-musl': 1.2.9 - '@rolldown/binding-linux-ppc64-gnu': 1.2.9 - '@rolldown/binding-linux-s390x-gnu': 1.2.9 - '@rolldown/binding-linux-x64-gnu': 1.2.9 - '@rolldown/binding-linux-x64-musl': 1.2.9 - '@rolldown/binding-openharmony-arm64': 1.2.9 - '@rolldown/binding-win32-arm64-msvc': 1.2.9 - '@rolldown/binding-win32-x64-msvc': 1.2.9 + '@rolldown/binding-android-arm-eabi': 1.2.10 + '@rolldown/binding-android-arm64': 1.2.10 + '@rolldown/binding-darwin-arm64': 1.2.10 + '@rolldown/binding-darwin-x64': 1.2.10 + '@rolldown/binding-freebsd-x64': 1.2.10 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.10 + '@rolldown/binding-linux-arm64-gnu': 1.2.10 + '@rolldown/binding-linux-arm64-musl': 1.2.10 + '@rolldown/binding-linux-ppc64-gnu': 1.2.10 + '@rolldown/binding-linux-s390x-gnu': 1.2.10 + '@rolldown/binding-linux-x64-gnu': 1.2.10 + '@rolldown/binding-linux-x64-musl': 1.2.10 + '@rolldown/binding-openharmony-arm64': 1.2.10 + '@rolldown/binding-win32-arm64-msvc': 1.2.10 + '@rolldown/binding-win32-x64-msvc': 1.2.10 - rollup-plugin-visualizer@7.1.1(rolldown@1.2.9): + rollup-plugin-visualizer@7.1.1(rolldown@1.2.10): dependencies: open: 11.0.4 picomatch: 4.0.7 source-map: 0.8.0 yargs: 18.2.0 optionalDependencies: - rolldown: 1.2.9 + rolldown: 1.2.10 run-applescript@7.1.0: {} @@ -6032,7 +6032,7 @@ snapshots: lightningcss: 1.33.0 picomatch: 4.0.7 postcss: 8.5.28 - rolldown: 1.2.9 + rolldown: 1.2.10 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.6.2 diff --git a/mock-api/package.json b/mock-api/package.json index ec99d5d92..ffc836250 100644 --- a/mock-api/package.json +++ b/mock-api/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^6.0.2", - "prettier": "^3.9.8" + "prettier": "^3.9.9" }, "packageManager": "pnpm@12.6.0" } diff --git a/mock-api/pnpm-lock.yaml b/mock-api/pnpm-lock.yaml index 016e5a7de..80e7ed185 100644 --- a/mock-api/pnpm-lock.yaml +++ b/mock-api/pnpm-lock.yaml @@ -175,10 +175,10 @@ importers: devDependencies: '@trivago/prettier-plugin-sort-imports': specifier: ^6.0.2 - version: 6.0.2(prettier@3.9.8) + version: 6.0.2(prettier@3.9.9) prettier: - specifier: ^3.9.8 - version: 3.9.8 + specifier: ^3.9.9 + version: 3.9.9 packages: @@ -303,8 +303,8 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - prettier@3.9.8: - resolution: {integrity: sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==} + prettier@3.9.9: + resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} engines: {node: '>=14'} hasBin: true @@ -373,7 +373,7 @@ snapshots: '@msgpack/msgpack@3.1.3': {} - '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.8)': + '@trivago/prettier-plugin-sort-imports@6.0.2(prettier@3.9.9)': dependencies: '@babel/generator': 7.29.8 '@babel/parser': 7.29.9 @@ -383,7 +383,7 @@ snapshots: lodash-es: 4.18.1 minimatch: 9.0.9 parse-imports-exports: 0.2.4 - prettier: 3.9.8 + prettier: 3.9.9 transitivePeerDependencies: - supports-color @@ -421,4 +421,4 @@ snapshots: picocolors@1.1.1: {} - prettier@3.9.8: {} + prettier@3.9.9: {} diff --git a/project-words.txt b/project-words.txt index 1a2a9624e..15da3c6be 100644 --- a/project-words.txt +++ b/project-words.txt @@ -1351,3 +1351,4 @@ zyxwvutsrqponmlkjihgfedcba öffnen česky živanović +neopixel \ No newline at end of file From f05105861edc7887e8f48ea79ae5a007f3510884 Mon Sep 17 00:00:00 2001 From: proddy Date: Thu, 24 Sep 2026 18:43:02 +0200 Subject: [PATCH 5/5] update doc --- CHANGELOG.md | 18 ++++++++++++++++++ CHANGELOG_LATEST.md | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c832fb2c..d86d0c8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.8.6] 24 September 2026 + +## Added + +- own rgb_led library supporting WS2812B V5 and the newer V6 RGB LEDs + +## [3.8.5] 22 September 2026 + +## Added + +- Change webUI message when upgrading to a major version of the firmware, in preparation for the upcoming 3.9.0 release. + +## [3.8.4] 4 August 2026 + +## Fixed + +- Allow auto-download and install of firmware versions which are smaller to allow update to the latest 3.9.0-development version. + ## [3.8.3] 1 August 2026 ## Added diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 7f7a5762c..00c9a1e54 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -18,7 +18,7 @@ This release is based on the latest Espressif/Arduino core version 3. It brings - remove HA config for excluded entities [#3229](https://github.com/emsesp/EMS-ESP32/issues/3229) - `show system` reports the Ethernet status on boards with a PHY, so a failed initialisation or a link that never came up is visible instead of Ethernet being omitted completely. Also shows the loop task's lowest free stack, to help diagnose reboots - new Application Setting "Automatically check for firmware updates", off by default. When enabled EMS-ESP asks emsesp.org for the latest version once a day on its own, otherwise it only asks when the WebUI wants to show it, so an unattended system never contacts emsesp.org -- Automatically check and convert older 3.8.x filesystem to the new 3.9.0 format +- automatically check and convert older 3.8.x filesystem to the new 3.9.0 format ## Fixed @@ -56,4 +56,5 @@ This release is based on the latest Espressif/Arduino core version 3. It brings - multiple optimization in network, power down WiFi when Ethernet and WiFi nosleep is instant when selected [#3213](https://github.com/emsesp/EMS-ESP32/pull/3213) - TLS buffers (~17KB per HTTPS request) are taken from PSRAM on boards that have it, instead of fragmenting the internal heap - failed versions.json fetches back off from 5 up to 160 minutes instead of retrying every 5 minutes, as each attempt blocks the main loop for as long as the DNS, connect and read timeouts allow +- optimized the LED library to support WS2812B V5 and the newer V6 RGB LEDs