6 Commits

Author SHA1 Message Date
Proddy
c572155d8d Merge pull request #3103 from proddy/core3
add missing "\r\n" to GET header
2026-05-29 16:18:45 +01:00
proddy
d79f27422f package update 2026-05-29 16:17:38 +01:00
proddy
844ee751b7 add esp32_exception_decoder 2026-05-29 16:16:05 +01:00
proddy
7b6d60691c add missing "\r\n" to GET header 2026-05-29 16:15:54 +01:00
proddy
c7816a644f prevent message command parsing URLs twice 2026-05-29 16:15:29 +01:00
proddy
a0196ff9b2 add comment 2026-05-29 16:14:59 +01:00
5 changed files with 37 additions and 19 deletions

View File

@@ -606,8 +606,8 @@ packages:
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
engines: {node: '>= 8.0.0'} engines: {node: '>= 8.0.0'}
'@rollup/pluginutils@5.3.0': '@rollup/pluginutils@5.4.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
peerDependencies: peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@@ -1185,8 +1185,8 @@ packages:
duplexer3@0.1.5: duplexer3@0.1.5:
resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==}
electron-to-chromium@1.5.363: electron-to-chromium@1.5.364:
resolution: {integrity: sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==} resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==}
emoji-regex@10.6.0: emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -3430,7 +3430,7 @@ snapshots:
'@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7)
'@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7) '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7)
'@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.14(@types/node@25.9.1)(terser@5.48.0)) '@prefresh/vite': 2.4.12(preact@10.29.2)(vite@8.0.14(@types/node@25.9.1)(terser@5.48.0))
'@rollup/pluginutils': 5.3.0 '@rollup/pluginutils': 5.4.0
babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.7)
debug: 4.4.3 debug: 4.4.3
magic-string: 0.30.21 magic-string: 0.30.21
@@ -3519,7 +3519,7 @@ snapshots:
estree-walker: 2.0.2 estree-walker: 2.0.2
picomatch: 2.3.2 picomatch: 2.3.2
'@rollup/pluginutils@5.3.0': '@rollup/pluginutils@5.4.0':
dependencies: dependencies:
'@types/estree': 1.0.9 '@types/estree': 1.0.9
estree-walker: 2.0.2 estree-walker: 2.0.2
@@ -3847,7 +3847,7 @@ snapshots:
dependencies: dependencies:
baseline-browser-mapping: 2.10.32 baseline-browser-mapping: 2.10.32
caniuse-lite: 1.0.30001793 caniuse-lite: 1.0.30001793
electron-to-chromium: 1.5.363 electron-to-chromium: 1.5.364
node-releases: 2.0.46 node-releases: 2.0.46
update-browserslist-db: 1.2.3(browserslist@4.28.2) update-browserslist-db: 1.2.3(browserslist@4.28.2)
@@ -4202,7 +4202,7 @@ snapshots:
duplexer3@0.1.5: {} duplexer3@0.1.5: {}
electron-to-chromium@1.5.363: {} electron-to-chromium@1.5.364: {}
emoji-regex@10.6.0: {} emoji-regex@10.6.0: {}

View File

@@ -85,7 +85,7 @@ build_unflags =
extra_scripts = extra_scripts =
post:scripts/rename_fw.py ; renames the firmware .bin file post:scripts/rename_fw.py ; renames the firmware .bin file
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = direct monitor_filters = direct, esp32_exception_decoder
build_type = release build_type = release
board_build.filesystem = littlefs board_build.filesystem = littlefs
board_build.littlefs_version = 2.0 board_build.littlefs_version = 2.0

View File

@@ -748,15 +748,19 @@ int http_request(std::string url, const std::string & method, const std::string
ssl_client->print(value.c_str()); ssl_client->print(value.c_str());
} else { } else {
ssl_client->println("Connection: close"); ssl_client->println("Connection: close");
ssl_client->print("\r\n"); // terminate headers - without this the server never responds
} }
auto ms = millis(); auto ms = millis();
while (ssl_client->connected() && !ssl_client->available() && millis() - ms < 3000) { while (ssl_client->connected() && !ssl_client->available() && millis() - ms < 3000) {
delay(0); delay(1);
} }
while (ssl_client->available()) { while (ssl_client->available()) {
result += (char)ssl_client->read(); result += (char)ssl_client->read();
} }
ssl_client->stop(); ssl_client->stop();
index = result.find_first_of(' '); index = result.find_first_of(' ');
if (index != std::string::npos) { if (index != std::string::npos) {
httpResult = stoi(result.substr(index + 1, 3)); httpResult = stoi(result.substr(index + 1, 3));
@@ -817,7 +821,8 @@ std::string compute(const std::string & expr) {
std::string value = doc[value_s] | ""; std::string value = doc[value_s] | "";
std::string method = doc[method_s] | "GET"; std::string method = doc[method_s] | "GET";
std::string result; std::string result;
int httpResult = http_request(url, method, value, doc[header_s].as<JsonObjectConst>(), result);
int httpResult = http_request(url, method, value, doc[header_s].as<JsonObjectConst>(), result);
if (httpResult == 200) { if (httpResult == 200) {
std::string key = doc[key_s] | ""; std::string key = doc[key_s] | "";
JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body JsonDocument keys_doc; // JsonDocument to hold "keys" after doc is parsed with HTTP body
@@ -847,6 +852,7 @@ std::string compute(const std::string & expr) {
} }
expr_new.replace(f, e - f, result); expr_new.replace(f, e - f, result);
} else if (httpResult != 0) { } else if (httpResult != 0) {
// httpResult of 0 means no url
EMSESP::logger().warning("URL command failed with https code: %d, response: %s", httpResult, result.c_str()); EMSESP::logger().warning("URL command failed with https code: %d, response: %s", httpResult, result.c_str());
} }
} }

View File

@@ -431,6 +431,18 @@ bool WebSchedulerService::onChange(const char * cmd) {
return false; return false;
} }
// system/message evaluates its own argument later (deferred via raw_value, computed in loop()),
// so pre-computing it here would make any {url} or expression inside it run twice. Pass
// system/message its value raw; compute() everything else as before.
// templated because ScheduleItem's strings use a PSRAM allocator, not std::string.
template <typename C, typename V>
static std::string compute_cmd_value(const C & cmd, const V & value) {
if (Helpers::toLower(cmd.c_str()) == "system/message") {
return std::string(value.c_str());
}
return compute(value.c_str());
}
// handle condition schedules, parse string stored in schedule.time field // handle condition schedules, parse string stored in schedule.time field
void WebSchedulerService::condition() { void WebSchedulerService::condition() {
for (ScheduleItem & scheduleItem : *scheduleItems_) { for (ScheduleItem & scheduleItem : *scheduleItems_) {
@@ -440,7 +452,7 @@ void WebSchedulerService::condition() {
// EMSESP::logger().debug("condition match: %s", match.c_str()); // EMSESP::logger().debug("condition match: %s", match.c_str());
#endif #endif
if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) { if (match.length() == 1 && match[0] == '1' && scheduleItem.retry_cnt == 0xFF) {
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())) ? 1 : 0xFF; scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value)) ? 1 : 0xFF;
} else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) { } else if (match.length() == 1 && match[0] == '0' && scheduleItem.retry_cnt == 1) {
scheduleItem.retry_cnt = 0xFF; scheduleItem.retry_cnt = 0xFF;
} else if (match.length() != 1) { // the match is not boolean } else if (match.length() != 1) { // the match is not boolean
@@ -472,13 +484,13 @@ void WebSchedulerService::loop() {
// check if we have onChange events // check if we have onChange events
while (!cmd_changed_.empty()) { while (!cmd_changed_.empty()) {
ScheduleItem si = *cmd_changed_.front(); ScheduleItem si = *cmd_changed_.front();
command(si.name, si.cmd.c_str(), compute(si.value.c_str())); command(si.name, si.cmd.c_str(), compute_cmd_value(si.cmd, si.value));
cmd_changed_.pop_front(); cmd_changed_.pop_front();
} }
for (ScheduleItem & scheduleItem : *scheduleItems_) { for (ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) { if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())); command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
// scheduleItem.active = false; // scheduleItem.active = false;
publish_single(scheduleItem.name, false); publish_single(scheduleItem.name, false);
if (EMSESP::mqtt_.get_publish_onchange(0)) { if (EMSESP::mqtt_.get_publish_onchange(0)) {
@@ -498,7 +510,7 @@ void WebSchedulerService::loop() {
if (last_tm_min == -2) { if (last_tm_min == -2) {
for (ScheduleItem & scheduleItem : *scheduleItems_) { for (ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) { if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min == 0) {
scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())) ? 0xFF : 0; scheduleItem.retry_cnt = command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value)) ? 0xFF : 0;
} }
} }
last_tm_min = -1; // startup done, now use for RTC last_tm_min = -1; // startup done, now use for RTC
@@ -516,7 +528,7 @@ void WebSchedulerService::loop() {
// scheduled timer commands // scheduled timer commands
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0 if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_TIMER && scheduleItem.elapsed_min > 0
&& (uptime_min % scheduleItem.elapsed_min == 0)) { && (uptime_min % scheduleItem.elapsed_min == 0)) {
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())); command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
} }
} }
last_uptime_min = uptime_min; last_uptime_min = uptime_min;
@@ -533,7 +545,7 @@ void WebSchedulerService::loop() {
for (const ScheduleItem & scheduleItem : *scheduleItems_) { for (const ScheduleItem & scheduleItem : *scheduleItems_) {
uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags; uint8_t dow = scheduleItem.flags & SCHEDULEFLAG_SCHEDULE_TIMER ? 0 : scheduleItem.flags;
if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) { if (scheduleItem.active && (real_dow & dow) && real_min == scheduleItem.elapsed_min) {
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())); command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
} }
} }
last_tm_min = tm->tm_min; last_tm_min = tm->tm_min;
@@ -545,7 +557,7 @@ bool WebSchedulerService::executeSchedule(const char * name) {
for (ScheduleItem & scheduleItem : *scheduleItems_) { for (ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE && strcmp(scheduleItem.name, name) == 0) { if (scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE && strcmp(scheduleItem.name, name) == 0) {
EMSESP::logger().info("Executing schedule '%s'", name); EMSESP::logger().info("Executing schedule '%s'", name);
return command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str())); return command(scheduleItem.name, scheduleItem.cmd.c_str(), compute_cmd_value(scheduleItem.cmd, scheduleItem.value));
} }
} }
EMSESP::logger().warning("Schedule '%s' not found", name); EMSESP::logger().warning("Schedule '%s' not found", name);

View File

@@ -470,7 +470,7 @@ bool WebStatusService::refresh_versions_cache() {
ssl_client.println("Connection: close"); ssl_client.println("Connection: close");
ssl_client.print("\r\n"); ssl_client.print("\r\n");
// wait for the first byte // wait for the first byte. The 5 seconds is GitHub's SLO
uint32_t ms = millis(); uint32_t ms = millis();
while (ssl_client.connected() && !ssl_client.available() && millis() - ms < 5000) { while (ssl_client.connected() && !ssl_client.available() && millis() - ms < 5000) {
delay(1); delay(1);