From bff9c6d7c597c54b4326a3d39ebb8f5b05aa98d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 10 Feb 2024 18:04:18 +0100 Subject: [PATCH 1/3] Support event data substitution in action commands --- include/hasp_conf.h | 4 ++ src/hasp/hasp_dispatch.cpp | 30 ++++++-- src/hasp/hasp_dispatch.h | 3 +- src/hasp/hasp_event.cpp | 89 +++++++++++++++--------- user_setups/darwin/darwin_sdl.ini | 3 +- user_setups/linux/linux_fbdev.ini | 3 +- user_setups/linux/linux_sdl.ini | 3 +- user_setups/win32/windows_gdi.ini | 3 +- user_setups/win32/windows_sdl_64bits.ini | 3 +- 9 files changed, 95 insertions(+), 46 deletions(-) diff --git a/include/hasp_conf.h b/include/hasp_conf.h index d7b666cbc..e36b8925c 100644 --- a/include/hasp_conf.h +++ b/include/hasp_conf.h @@ -181,6 +181,10 @@ #define HASP_NUM_GPIO_CONFIG 8 #endif +#ifndef HASP_USE_EVENT_DATA_SUBST +#define HASP_USE_EVENT_DATA_SUBST 0 +#endif + // #ifndef HASP_USE_CUSTOM // #define HASP_USE_CUSTOM 0 // #endif diff --git a/src/hasp/hasp_dispatch.cpp b/src/hasp/hasp_dispatch.cpp index d9555c8ae..01408211f 100644 --- a/src/hasp/hasp_dispatch.cpp +++ b/src/hasp/hasp_dispatch.cpp @@ -646,11 +646,17 @@ void dispatch_screenshot(const char*, const char* filename, uint8_t source) } bool dispatch_json_variant(JsonVariant& json, uint8_t& savedPage, uint8_t source) +{ + JsonObject dummy; + return dispatch_json_variant_with_data(json, savedPage, source, dummy); +} + +bool dispatch_json_variant_with_data(JsonVariant& json, uint8_t& savedPage, uint8_t source, JsonObject& data) { if(json.is()) { // handle json as an array of commands LOG_DEBUG(TAG_MSGR, "Json ARRAY"); for(JsonVariant command : json.as()) { - dispatch_json_variant(command, savedPage, source); + dispatch_json_variant_with_data(command, savedPage, source, data); } } else if(json.is()) { // handle json as a jsonl @@ -658,13 +664,23 @@ bool dispatch_json_variant(JsonVariant& json, uint8_t& savedPage, uint8_t source hasp_new_object(json.as(), savedPage); } else if(json.is()) { // handle json as a single command - LOG_DEBUG(TAG_MSGR, "Json text = %s", json.as().c_str()); - dispatch_simple_text_command(json.as().c_str(), source); - - } else if(json.is()) { // handle json as a single command - LOG_DEBUG(TAG_MSGR, "Json text = %s", json.as()); - dispatch_simple_text_command(json.as(), source); + std::string command = json.as(); + +#if HASP_USE_EVENT_DATA_SUBST + if(command.find('%') != std::string::npos) { + // '%' found in command, run variable substitution + for(JsonPair kv : data) { + std::string find = "%" + std::string(kv.key().c_str()) + "%"; + std::string replace = kv.value().as(); + size_t pos = command.find(find); + if(pos == std::string::npos) continue; + command.replace(pos, find.length(), replace); + } + } +#endif + LOG_DEBUG(TAG_MSGR, "Json text = %s", command.c_str()); + dispatch_simple_text_command(command.c_str(), source); } else if(json.isNull()) { // event handler not found // nothing to do diff --git a/src/hasp/hasp_dispatch.h b/src/hasp/hasp_dispatch.h index 13967871c..d9f034c58 100644 --- a/src/hasp/hasp_dispatch.h +++ b/src/hasp/hasp_dispatch.h @@ -64,6 +64,7 @@ void dispatch_parse_jsonl(Stream& stream, uint8_t& saved_page_id); void dispatch_parse_jsonl(std::istream& stream, uint8_t& saved_page_id); #endif bool dispatch_json_variant(JsonVariant& json, uint8_t& savedPage, uint8_t source); +bool dispatch_json_variant_with_data(JsonVariant& json, uint8_t& savedPage, uint8_t source, JsonObject& data); void dispatch_clear_page(const char* page); void dispatch_json_error(uint8_t tag, DeserializationError& jsonError); @@ -110,4 +111,4 @@ struct haspCommand_t void (*func)(const char*, const char*, uint8_t); }; -#endif \ No newline at end of file +#endif diff --git a/src/hasp/hasp_event.cpp b/src/hasp/hasp_event.cpp index fd5eeaeef..9a5cc2bff 100644 --- a/src/hasp/hasp_event.cpp +++ b/src/hasp/hasp_event.cpp @@ -42,23 +42,47 @@ void event_reset_last_value_sent() last_value_sent = INT16_MIN; } -void script_event_handler(const char* eventname, const char* json) +static bool script_event_handler(const char* eventname, const char* action, const char* data) { + if(action == NULL) { + LOG_DEBUG(TAG_EVENT, F("Skipping event: name=%s, data=%s"), eventname, data); + return false; + } StaticJsonDocument<256> doc; StaticJsonDocument<64> filter; filter[eventname] = true; - DeserializationError jsonError = deserializeJson(doc, json, DeserializationOption::Filter(filter)); + DeserializationError jsonError = deserializeJson(doc, action, DeserializationOption::Filter(filter)); if(!jsonError) { - JsonVariant json = doc[eventname].as(); + JsonVariant json = doc[eventname].as(); + if(json.isNull()) { + LOG_DEBUG(TAG_EVENT, F("Skipping event: name=%s, data=%s"), eventname, data); + return true; + } else { + LOG_DEBUG(TAG_EVENT, F("Handling event: name=%s, data=%s"), eventname, data); + } + + JsonObject dataJson; +#if HASP_USE_EVENT_DATA_SUBST + if(data != NULL) { + StaticJsonDocument<256> jsonDoc; + if(deserializeJson(jsonDoc, data) != DeserializationError::Ok) { + LOG_ERROR(TAG_EVENT, F("Deserialization failed")); + } else { + dataJson = jsonDoc.as(); + } + } +#endif + uint8_t savedPage = haspPages.get(); - if(!dispatch_json_variant(json, savedPage, TAG_EVENT)) { + if(!dispatch_json_variant_with_data(json, savedPage, TAG_EVENT, dataJson)) { LOG_WARNING(TAG_EVENT, F(D_DISPATCH_COMMAND_NOT_FOUND), eventname); } } else { dispatch_json_error(TAG_EVENT, jsonError); } + return true; } /** @@ -254,11 +278,16 @@ static bool translate_event(lv_obj_t* obj, lv_event_t event, uint8_t& eventid) // ##################### Value Senders ######################################################## -static void event_send_object_data(lv_obj_t* obj, const char* data) +static void event_send_object_data(lv_obj_t* obj, const char* eventname, const char* data) { uint8_t pageid; uint8_t objid; + // handle object "action", abort sending if handled + if(script_event_handler(eventname, my_obj_get_action(obj), data)) { + return; + } + if(hasp_find_id_from_obj(obj, &pageid, &objid)) { if(!data) return; object_dispatch_state(pageid, objid, data); @@ -271,23 +300,23 @@ static void event_send_object_data(lv_obj_t* obj, const char* data) static void event_object_val_event(lv_obj_t* obj, uint8_t eventid, int16_t val) { char data[512]; + char eventname[8]; { - char eventname[8]; Parser::get_event_name(eventid, eventname, sizeof(eventname)); if(const char* tag = my_obj_get_tag(obj)) snprintf_P(data, sizeof(data), PSTR("{\"event\":\"%s\",\"val\":%d,\"tag\":%s}"), eventname, val, tag); else snprintf_P(data, sizeof(data), PSTR("{\"event\":\"%s\",\"val\":%d}"), eventname, val); } - event_send_object_data(obj, data); + event_send_object_data(obj, eventname, data); } // Send out events with a val and text attribute static void event_object_selection_changed(lv_obj_t* obj, uint8_t eventid, int16_t val, const char* text) { char data[512]; + char eventname[8]; { - char eventname[8]; Parser::get_event_name(eventid, eventname, sizeof(eventname)); if(const char* tag = my_obj_get_tag(obj)) @@ -296,7 +325,7 @@ static void event_object_selection_changed(lv_obj_t* obj, uint8_t eventid, int16 else snprintf_P(data, sizeof(data), PSTR("{\"event\":\"%s\",\"val\":%d,\"text\":\"%s\"}"), eventname, val, text); } - event_send_object_data(obj, data); + event_send_object_data(obj, eventname, data); } // ##################### Event Handlers ######################################################## @@ -402,16 +431,16 @@ void swipe_event_handler(lv_obj_t* obj, lv_event_t event) lv_gesture_dir_t dir = lv_indev_get_gesture_dir(lv_indev_get_act()); switch(dir) { case LV_GESTURE_DIR_LEFT: - script_event_handler("left", swipe); + script_event_handler("left", swipe, NULL); break; case LV_GESTURE_DIR_RIGHT: - script_event_handler("right", swipe); + script_event_handler("right", swipe, NULL); break; case LV_GESTURE_DIR_BOTTOM: - script_event_handler("down", swipe); + script_event_handler("down", swipe, NULL); break; default: - script_event_handler("up", swipe); + script_event_handler("up", swipe, NULL); } } } @@ -432,8 +461,8 @@ void textarea_event_handler(lv_obj_t* obj, lv_event_t event) if(!translate_event(obj, event, hasp_event_id)) return; char data[1024]; + char eventname[8]; { - char eventname[8]; Parser::get_event_name(hasp_event_id, eventname, sizeof(eventname)); if(const char* tag = my_obj_get_tag(obj)) @@ -444,7 +473,7 @@ void textarea_event_handler(lv_obj_t* obj, lv_event_t event) lv_textarea_get_text(obj)); } - event_send_object_data(obj, data); + event_send_object_data(obj, eventname, data); } else if(event == LV_EVENT_FOCUSED) { lv_textarea_set_cursor_hidden(obj, false); } else if(event == LV_EVENT_DEFOCUSED) { @@ -518,23 +547,17 @@ void generic_event_handler(lv_obj_t* obj, lv_event_t event) if(last_value_sent == HASP_EVENT_LOST) return; - if(const char* action = my_obj_get_action(obj)) { - char eventname[8]; + char data[512]; + char eventname[8]; + { Parser::get_event_name(last_value_sent, eventname, sizeof(eventname)); - script_event_handler(eventname, action); - } else { - char data[512]; - { - char eventname[8]; - Parser::get_event_name(last_value_sent, eventname, sizeof(eventname)); - if(const char* tag = my_obj_get_tag(obj)) - snprintf_P(data, sizeof(data), PSTR("{\"event\":\"%s\",\"tag\":%s}"), eventname, tag); - else - snprintf_P(data, sizeof(data), PSTR("{\"event\":\"%s\"}"), eventname); - } - event_send_object_data(obj, data); + if(const char* tag = my_obj_get_tag(obj)) + snprintf_P(data, sizeof(data), PSTR("{\"event\":\"%s\",\"tag\":%s}"), eventname, tag); + else + snprintf_P(data, sizeof(data), PSTR("{\"event\":\"%s\"}"), eventname); } + event_send_object_data(obj, eventname, data); // Update group objects and gpios on release if(last_value_sent != LV_EVENT_LONG_PRESSED && last_value_sent != LV_EVENT_LONG_PRESSED_REPEAT) { @@ -841,8 +864,8 @@ void cpicker_event_handler(lv_obj_t* obj, lv_event_t event) if(hasp_event_id == HASP_EVENT_CHANGED && last_color_sent.full == color.full) return; // same value as before char data[512]; + char eventname[8]; { - char eventname[8]; Parser::get_event_name(hasp_event_id, eventname, sizeof(eventname)); lv_color32_t c32; @@ -864,7 +887,7 @@ void cpicker_event_handler(lv_obj_t* obj, lv_event_t event) eventname, c32.ch.red, c32.ch.green, c32.ch.blue, c32.ch.red, c32.ch.green, c32.ch.blue, hsv.h, hsv.s, hsv.v); } - event_send_object_data(obj, data); + event_send_object_data(obj, eventname, data); // event_update_group(obj->user_data.groupid, obj, val, min, max); } @@ -891,8 +914,8 @@ void calendar_event_handler(lv_obj_t* obj, lv_event_t event) return; // same object and value as before char data[512]; + char eventname[8]; { - char eventname[8]; Parser::get_event_name(hasp_event_id, eventname, sizeof(eventname)); last_value_sent = val; @@ -907,7 +930,7 @@ void calendar_event_handler(lv_obj_t* obj, lv_event_t event) PSTR("{\"event\":\"%s\",\"val\":\"%d\",\"text\":\"%04d-%02d-%02dT00:00:00Z\"}"), eventname, date->day, date->year, date->month, date->day); } - event_send_object_data(obj, data); + event_send_object_data(obj, eventname, data); // event_update_group(obj->user_data.groupid, obj, val, min, max); } diff --git a/user_setups/darwin/darwin_sdl.ini b/user_setups/darwin/darwin_sdl.ini index b1c4057a6..25f7e96a5 100644 --- a/user_setups/darwin/darwin_sdl.ini +++ b/user_setups/darwin/darwin_sdl.ini @@ -34,7 +34,8 @@ build_flags = -D HASP_USE_GIFDECODE=0 -D HASP_USE_JPGDECODE=0 -D HASP_USE_MQTT=1 - -D MQTT_MAX_PACKET_SIZE=2048 + -D HASP_USE_EVENT_DATA_SUBST=1 + -D MQTT_MAX_PACKET_SIZE=32768 -D HASP_ATTRIBUTE_FAST_MEM= -D IRAM_ATTR= ; No IRAM_ATTR available -D PROGMEM= ; No PROGMEM available diff --git a/user_setups/linux/linux_fbdev.ini b/user_setups/linux/linux_fbdev.ini index 1927c7abb..d870d02b6 100644 --- a/user_setups/linux/linux_fbdev.ini +++ b/user_setups/linux/linux_fbdev.ini @@ -31,7 +31,8 @@ build_flags = -D HASP_USE_JPGDECODE=0 -D HASP_USE_MQTT=1 -D HASP_USE_LVGL_TASK=1 - -D MQTT_MAX_PACKET_SIZE=2048 + -D HASP_USE_EVENT_DATA_SUBST=1 + -D MQTT_MAX_PACKET_SIZE=32768 -D HASP_ATTRIBUTE_FAST_MEM= -D IRAM_ATTR= ; No IRAM_ATTR available -D PROGMEM= ; No PROGMEM available diff --git a/user_setups/linux/linux_sdl.ini b/user_setups/linux/linux_sdl.ini index 2445b8b01..6c0edb1f6 100644 --- a/user_setups/linux/linux_sdl.ini +++ b/user_setups/linux/linux_sdl.ini @@ -34,7 +34,8 @@ build_flags = -D HASP_USE_GIFDECODE=0 -D HASP_USE_JPGDECODE=0 -D HASP_USE_MQTT=1 - -D MQTT_MAX_PACKET_SIZE=2048 + -D HASP_USE_EVENT_DATA_SUBST=1 + -D MQTT_MAX_PACKET_SIZE=32768 -D HASP_ATTRIBUTE_FAST_MEM= -D IRAM_ATTR= ; No IRAM_ATTR available -D PROGMEM= ; No PROGMEM available diff --git a/user_setups/win32/windows_gdi.ini b/user_setups/win32/windows_gdi.ini index c887ffd88..1cfc8bed7 100644 --- a/user_setups/win32/windows_gdi.ini +++ b/user_setups/win32/windows_gdi.ini @@ -30,7 +30,8 @@ build_flags = -D HASP_USE_MQTT=1 -D HASP_USE_SYSLOG=0 -D HASP_USE_LVGL_TASK=1 - -D MQTT_MAX_PACKET_SIZE=2048 + -D HASP_USE_EVENT_DATA_SUBST=1 + -D MQTT_MAX_PACKET_SIZE=32768 -D HASP_ATTRIBUTE_FAST_MEM= -D IRAM_ATTR= ; No IRAM_ATTR available -D PROGMEM= ; No PROGMEM available diff --git a/user_setups/win32/windows_sdl_64bits.ini b/user_setups/win32/windows_sdl_64bits.ini index b51ecfb44..2f4ec423e 100644 --- a/user_setups/win32/windows_sdl_64bits.ini +++ b/user_setups/win32/windows_sdl_64bits.ini @@ -33,7 +33,8 @@ build_flags = -D HASP_USE_JPGDECODE=0 -D HASP_USE_MQTT=1 -D HASP_USE_SYSLOG=0 - -D MQTT_MAX_PACKET_SIZE=2048 + -D HASP_USE_EVENT_DATA_SUBST=1 + -D MQTT_MAX_PACKET_SIZE=32768 -D HASP_ATTRIBUTE_FAST_MEM= -D IRAM_ATTR= ; No IRAM_ATTR available -D PROGMEM= ; No PROGMEM available From 72973e1708f3193cde0cde6bf0332ca8971c1fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 10 Feb 2024 20:30:12 +0100 Subject: [PATCH 2/3] Implement command scheduling --- src/hasp/hasp_dispatch.cpp | 87 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/hasp/hasp_dispatch.cpp b/src/hasp/hasp_dispatch.cpp index 01408211f..5bde76029 100644 --- a/src/hasp/hasp_dispatch.cpp +++ b/src/hasp/hasp_dispatch.cpp @@ -37,6 +37,8 @@ #endif #endif +#include + dispatch_conf_t dispatch_setings = {.teleperiod = 300}; uint16_t dispatchSecondsToNextTeleperiod = 0; @@ -48,6 +50,8 @@ haspCommand_t commands[28]; moodlight_t moodlight = {.brightness = 255}; uint8_t saved_jsonl_page = 0; +static std::map scheduled_tasks; + /* Sends the payload out on the state/subtopic */ void dispatch_state_subtopic(const char* subtopic, const char* payload) @@ -1463,6 +1467,88 @@ void dispatch_sleep(const char*, const char*, uint8_t source) hasp_set_wakeup_touch(false); } +static void dispatch_schedule_runner(lv_task_t* task) +{ + dispatch_text_line((const char*)task->user_data, TAG_MSGR); +} + +void dispatch_schedule(const char*, const char* payload, uint8_t source) +{ + // duplicate the string for modification + char* argline = strdup(payload); + // split argline into argv by whitespace; limit to 4 parts + char* argv[4]; + int argn = 0; + argv[argn++] = argline; + while(*argline) { + if(*argline == ' ') { + *argline = '\0'; + argv[argn++] = argline + 1; + } + argline++; + if(argn == 4) break; + } + + const char* action = argv[0]; + uint32_t id = atoi(argv[1]); + + if(strcasecmp(action, "set") == 0) { + if(argn != 4) { + LOG_ERROR(TAG_MSGR, F("Usage: schedule set ")); + goto end; + } + uint32_t period = atoi(argv[2]); + const char* command = strdup(argv[3]); + + if(scheduled_tasks.find(id) != scheduled_tasks.end()) { + // update an existing task + lv_task_t* task = scheduled_tasks[id]; + free(task->user_data); + task->period = period; + task->user_data = (void*)command; + } else { + // create a new task + lv_task_t* task = lv_task_create(dispatch_schedule_runner, period, LV_TASK_PRIO_MID, (void*)command); + scheduled_tasks[id] = task; + } + + LOG_INFO(TAG_MSGR, F("Scheduled command ID %u, every %u ms: %s"), id, period, command); + + goto end; + } + + if(argn != 2) { + LOG_ERROR(TAG_MSGR, F("Usage: schedule ")); + goto end; + } + if(scheduled_tasks.find(id) == scheduled_tasks.end()) { + LOG_ERROR(TAG_MSGR, F("Task by ID %u does not exist"), id); + goto end; + } + + { + lv_task_t* task = scheduled_tasks[id]; + if(strcasecmp(action, "del") == 0) { + free(task->user_data); + lv_task_del(task); + scheduled_tasks.erase(id); + goto end; + } + if(strcasecmp(action, "start") == 0) { + lv_task_set_prio(task, LV_TASK_PRIO_MID); + goto end; + } + if(strcasecmp(action, "stop") == 0) { + lv_task_set_prio(task, LV_TASK_PRIO_OFF); + goto end; + } + } + +end: + // release the duplicated argline + free(argv[0]); +} + void dispatch_idle_state(uint8_t state) { char topic[8]; @@ -1596,6 +1682,7 @@ void dispatchSetup() dispatch_add_command(PSTR("moodlight"), dispatch_moodlight); dispatch_add_command(PSTR("idle"), dispatch_idle); dispatch_add_command(PSTR("sleep"), dispatch_sleep); + dispatch_add_command(PSTR("schedule"), dispatch_schedule); dispatch_add_command(PSTR("statusupdate"), dispatch_statusupdate); dispatch_add_command(PSTR("clearpage"), dispatch_clear_page); dispatch_add_command(PSTR("clearfont"), dispatch_clear_font); From afbebdbce1eae926dd92b21153861dd47e0d243d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Sat, 10 Feb 2024 23:58:37 +0100 Subject: [PATCH 3/3] Allow publishing MQTT message even with action tag --- src/hasp/hasp_event.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hasp/hasp_event.cpp b/src/hasp/hasp_event.cpp index 9a5cc2bff..4f06e5171 100644 --- a/src/hasp/hasp_event.cpp +++ b/src/hasp/hasp_event.cpp @@ -52,13 +52,14 @@ static bool script_event_handler(const char* eventname, const char* action, cons StaticJsonDocument<64> filter; filter[eventname] = true; + filter["pub"] = true; DeserializationError jsonError = deserializeJson(doc, action, DeserializationOption::Filter(filter)); if(!jsonError) { JsonVariant json = doc[eventname].as(); if(json.isNull()) { LOG_DEBUG(TAG_EVENT, F("Skipping event: name=%s, data=%s"), eventname, data); - return true; + goto end; } else { LOG_DEBUG(TAG_EVENT, F("Handling event: name=%s, data=%s"), eventname, data); } @@ -82,6 +83,9 @@ static bool script_event_handler(const char* eventname, const char* action, cons } else { dispatch_json_error(TAG_EVENT, jsonError); } + +end: + if(doc["pub"].is()) return !doc["pub"].as(); return true; }