diff --git a/include/lv_conf_v7.h b/include/lv_conf_v7.h index 7b206dfe..875ad391 100644 --- a/include/lv_conf_v7.h +++ b/include/lv_conf_v7.h @@ -787,12 +787,13 @@ typedef void* lv_font_user_data_t; /*Declare the type of the user data of object (can be e.g. `void *`, `int`, `struct`)*/ typedef struct { uint8_t id:8; - uint8_t objid:8; + uint8_t objid:6; uint8_t transitionid:4; uint8_t actionid:4; uint8_t groupid:4; uint8_t swipeid:4; void* tag; + char* action; } lv_obj_user_data_t; /*1: enable `lv_obj_realaign()` based on `lv_obj_align()` parameters*/ diff --git a/src/hasp/hasp_attribute.cpp b/src/hasp/hasp_attribute.cpp index f8927470..c59b153e 100644 --- a/src/hasp/hasp_attribute.cpp +++ b/src/hasp/hasp_attribute.cpp @@ -1630,6 +1630,18 @@ static hasp_attribute_type_t attribute_common_tag(lv_obj_t* obj, uint16_t attr_h } break; // attribute_found + case ATTR_ACTION: + if(update) { + my_obj_set_action(obj, payload); + } else { + if(my_obj_get_action(obj)) { + *text = (char*)my_obj_get_action(obj); + } else { + strcpy_P(*text, "null"); // TODO : Literal String + } + } + break; // attribute_found + default: return HASP_ATTR_TYPE_NOT_FOUND; } @@ -2636,6 +2648,7 @@ void hasp_process_obj_attribute(lv_obj_t* obj, const char* attribute, const char ret = attribute_common_align(obj, attribute, payload, &text, update); break; case ATTR_TAG: + case ATTR_ACTION: ret = attribute_common_tag(obj, attr_hash, payload, &text, update); break; case ATTR_JSONL: @@ -2662,13 +2675,13 @@ void hasp_process_obj_attribute(lv_obj_t* obj, const char* attribute, const char // case ATTR_BTN_POS: - case ATTR_ACTION: - if(update) - obj->user_data.actionid = Parser::get_action_id(payload); - else - val = obj->user_data.actionid; - ret = HASP_ATTR_TYPE_INT; - break; + /* case ATTR_ACTION: + if(update) + obj->user_data.actionid = Parser::get_action_id(payload); + else + val = obj->user_data.actionid; + ret = HASP_ATTR_TYPE_INT; + break;*/ // case ATTR_SYMBOL: // (update) ? lv_dropdown_set_symbol(obj, payload) : diff --git a/src/hasp/hasp_attribute.h b/src/hasp/hasp_attribute.h index 8fb39ace..9a1e24d9 100644 --- a/src/hasp/hasp_attribute.h +++ b/src/hasp/hasp_attribute.h @@ -16,6 +16,7 @@ lv_chart_series_t* my_chart_get_series(lv_obj_t* chart, uint8_t ser_num); void my_obj_set_value_str_text(lv_obj_t* obj, uint8_t part, lv_state_t state, const char* text); void my_obj_set_tag(lv_obj_t* obj, const char* tag); +void my_obj_set_action(lv_obj_t* obj, const char* tag); const char* my_obj_get_tag(lv_obj_t* obj); void my_btnmatrix_map_clear(lv_obj_t* obj); void my_msgbox_map_clear(lv_obj_t* obj); @@ -482,8 +483,8 @@ _HASP_ATTRIBUTE(SCALE_END_LINE_WIDTH, scale_end_line_width, lv_style_int_t) // Spinner #define ATTR_SPEED 14375 #define ATTR_THICKNESS 24180 -//#define ATTR_ARC_LENGTH 755 - use ATTR_ANGLE -// #define ATTR_DIRECTION 32415 - see Dropdown +// #define ATTR_ARC_LENGTH 755 - use ATTR_ANGLE +// #define ATTR_DIRECTION 32415 - see Dropdown // Line #define ATTR_POINTS 8643 diff --git a/src/hasp/hasp_attribute_helper.h b/src/hasp/hasp_attribute_helper.h index 1c711e67..929df661 100644 --- a/src/hasp/hasp_attribute_helper.h +++ b/src/hasp/hasp_attribute_helper.h @@ -95,6 +95,42 @@ const char* my_obj_get_tag(lv_obj_t* obj) return (char*)obj->user_data.tag; } +// the action data is stored as SERIALIZED JSON data +void my_obj_set_action(lv_obj_t* obj, const char* action) +{ + // release old action + if(obj->user_data.action) { + hasp_free(obj->user_data.action); + obj->user_data.action = NULL; + } + + // new action is blank + if(action == NULL || action[0] == '\0') return; + + // create new action + { + StaticJsonDocument<512> doc; + // size_t len = action ? strlen(action) : 0; + + // check if it is a proper JSON object + DeserializationError error = deserializeJson(doc, action /*, len*/); + if(error != DeserializationError::Ok) doc.set(action); // use tag as-is + + const size_t size = measureJson(doc) + 1; + if(char* str = (char*)hasp_malloc(size)) { + size_t len = serializeJson(doc, str, size); // tidy-up the json object + obj->user_data.action = str; + LOG_VERBOSE(TAG_ATTR, "new json: %s", str); + } + } +} + +// the tag data is stored as SERIALIZED JSON data +const char* my_obj_get_action(lv_obj_t* obj) +{ + return obj->user_data.action; +} + lv_label_align_t my_textarea_get_text_align(lv_obj_t* ta) { lv_textarea_ext_t* ext = (lv_textarea_ext_t*)lv_obj_get_ext_attr(ta); diff --git a/src/hasp/hasp_dispatch.cpp b/src/hasp/hasp_dispatch.cpp index d4ce7d2b..7b8952bd 100644 --- a/src/hasp/hasp_dispatch.cpp +++ b/src/hasp/hasp_dispatch.cpp @@ -4,13 +4,13 @@ #include #include -//#include "ArduinoLog.h" +// #include "ArduinoLog.h" #include "hasplib.h" #include "dev/device.h" #include "drv/tft/tft_driver.h" -//#include "hasp_gui.h" +// #include "hasp_gui.h" #if HASP_USE_DEBUG > 0 #include "../hasp_debug.h" @@ -272,7 +272,7 @@ static void dispatch_output(const char* topic, const char* payload) } // objectattribute=value -void dispatch_command(const char* topic, const char* payload, bool update, uint8_t source) +static void dispatch_command(const char* topic, const char* payload, bool update, uint8_t source) { /* ================================= Standard payload commands ======================================= */ @@ -323,47 +323,14 @@ void dispatch_command(const char* topic, const char* payload, bool update, uint8 #endif // HASP_USE_CONFIG } else { if(strlen(payload) == 0) { - // dispatch_text_line(topic); // Could cause an infinite loop! + // dispatch_simple_text_command(topic); // Could cause an infinite loop! } LOG_WARNING(TAG_MSGR, F(D_DISPATCH_COMMAND_NOT_FOUND " => %s"), topic, payload); } } -// Strip command/config prefix from the topic and process the payload -void dispatch_topic_payload(const char* topic, const char* payload, bool update, uint8_t source) -{ - if(!strcmp_P(topic, PSTR(MQTT_TOPIC_COMMAND))) { - dispatch_text_line((char*)payload, source); - return; - } - - if(topic == strstr_P(topic, PSTR(MQTT_TOPIC_COMMAND "/"))) { // startsWith command/ - topic += 8u; - dispatch_command(topic, (char*)payload, update, source); - return; - } - -#if HASP_USE_CONFIG > 0 - if(topic == strstr_P(topic, PSTR("config/"))) { // startsWith config/ - topic += 7u; - dispatch_config(topic, (char*)payload, source); - return; - } -#endif - -#if defined(HASP_USE_CUSTOM) - if(topic == strstr_P(topic, PSTR(MQTT_TOPIC_CUSTOM "/"))) { // startsWith custom - topic += 7u; - custom_topic_payload(topic, (char*)payload, source); - return; - } -#endif - - dispatch_command(topic, (char*)payload, update, source); // dispatch as is -} - // Parse one line of text and execute the command -void dispatch_text_line(const char* cmnd, uint8_t source) +static void dispatch_simple_text_command(const char* cmnd, uint8_t source) { while(cmnd[0] == ' ' || cmnd[0] == '\t') cmnd++; // skip leading spaces if(cmnd[0] == '/' && cmnd[1] == '/') return; // comment @@ -381,9 +348,9 @@ void dispatch_text_line(const char* cmnd, uint8_t source) dispatch_command("json", cmnd, false, source); break; // comment - case ' ': - dispatch_text_line(cmnd, source); - break; + // case ' ': + // dispatch_simple_text_command(cmnd, source); + // break; default: { size_t pos1 = std::string(cmnd).find("="); @@ -425,6 +392,39 @@ void dispatch_text_line(const char* cmnd, uint8_t source) } } +// Strip command/config prefix from the topic and process the payload +void dispatch_topic_payload(const char* topic, const char* payload, bool update, uint8_t source) +{ + if(!strcmp_P(topic, PSTR(MQTT_TOPIC_COMMAND))) { + dispatch_simple_text_command((char*)payload, source); + return; + } + + if(topic == strstr_P(topic, PSTR(MQTT_TOPIC_COMMAND "/"))) { // startsWith command/ + topic += 8u; + dispatch_command(topic, (char*)payload, update, source); + return; + } + +#if HASP_USE_CONFIG > 0 + if(topic == strstr_P(topic, PSTR("config/"))) { // startsWith config/ + topic += 7u; + dispatch_config(topic, (char*)payload, source); + return; + } +#endif + +#if defined(HASP_USE_CUSTOM) + if(topic == strstr_P(topic, PSTR(MQTT_TOPIC_CUSTOM "/"))) { // startsWith custom + topic += 7u; + custom_topic_payload(topic, (char*)payload, source); + return; + } +#endif + + dispatch_command(topic, (char*)payload, update, source); // dispatch as is +} + // void dispatch_output_group_state(uint8_t groupid, uint16_t state) // { // char payload[64]; @@ -593,54 +593,69 @@ void dispatch_screenshot(const char*, const char* filename, uint8_t source) #endif } -void dispatch_parse_json(const char*, const char* payload, uint8_t source) -{ // Parse an incoming JSON array into individual commands - /* if(strPayload.endsWith(",]")) { - // Trailing null array elements are an artifact of older Home Assistant automations - // and need to be removed before parsing by ArduinoJSON 6+ - strPayload.remove(strPayload.length() - 2, 2); - strPayload.concat("]"); - }*/ - // size_t maxsize = (128u * ((strlen(payload) / 128) + 1)) + 512; - // DynamicJsonDocument json(maxsize); - StaticJsonDocument<1024> json; - - // Note: Deserialization needs to be (const char *) so the objects WILL be copied - // this uses more memory but otherwise the mqtt receive buffer can get overwritten by the send buffer !! - DeserializationError jsonError = deserializeJson(json, payload); - // json.shrinkToFit(); - - if(jsonError) { // Couldn't parse incoming JSON command - dispatch_json_error(TAG_MSGR, jsonError); - - } else if(json.is()) { // handle json as an array of commands +bool dispatch_json_variant(JsonVariant& json, uint8_t& savedPage, uint8_t source) +{ + if(json.is()) { // handle json as an array of commands JsonArray arr = json.as(); - // guiStop(); + LOG_WARNING(TAG_MSGR, "TEXT = ARRAY"); for(JsonVariant command : arr) { - dispatch_text_line(command.as(), source); + dispatch_json_variant(command, savedPage, source); } - // guiStart(); + } else if(json.is()) { // handle json as a jsonl - uint8_t savedPage = haspPages.get(); + LOG_WARNING(TAG_MSGR, "TEXT = OBJECT"); hasp_new_object(json.as(), savedPage); - // #ifdef ARDUINO - // } else if(json.is()) { // handle json as a single command - // dispatch_text_line(json.as().c_str()); - // #else } else if(json.is()) { // handle json as a single command - dispatch_text_line(json.as().c_str(), source); - // #endif + LOG_WARNING(TAG_MSGR, "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 - dispatch_text_line(json.as(), source); - - // } else if(json.is()) { // handle json as a single command - // dispatch_text_line(json.as()); + LOG_WARNING(TAG_MSGR, "TEXT = %s", json.as()); + dispatch_simple_text_command(json.as(), source); } else { - LOG_WARNING(TAG_MSGR, F(D_DISPATCH_COMMAND_NOT_FOUND), payload); + LOG_WARNING(TAG_MSGR, "TEXT = unknown type"); + return false; } + return true; +} + +void dispatch_text_line(const char* payload, uint8_t source) +{ + + { + // size_t maxsize = (128u * ((strlen(payload) / 128) + 1)) + 512; + // DynamicJsonDocument json(maxsize); + StaticJsonDocument<1024> doc; + + // Note: Deserialization needs to be (const char *) so the objects WILL be copied + // this uses more memory but otherwise the mqtt receive buffer can get overwritten by the send buffer !! + DeserializationError jsonError = deserializeJson(doc, payload); + // json.shrinkToFit(); + + if(jsonError) { + // dispatch_json_error(TAG_MSGR, jsonError); + + } else { + JsonVariant json = doc.as(); + uint8_t savedPage = haspPages.get(); + if(!dispatch_json_variant(json, savedPage, source)) { + LOG_WARNING(TAG_MSGR, F(D_DISPATCH_COMMAND_NOT_FOUND), payload); + // dispatch_simple_text_command(payload, source); + } + + return; + } + } + + // Could not parse as json + dispatch_simple_text_command(payload, source); +} + +void dispatch_parse_json(const char*, const char* payload, uint8_t source) +{ // Parse an incoming JSON array into individual commands + dispatch_simple_text_command(payload, source); } #ifdef ARDUINO @@ -730,7 +745,7 @@ void dispatch_run_script(const char*, const char* payload, uint8_t source) index++; } if(index > 0 && buffer.charAt(0) != '#') { // Check for comments - dispatch_text_line(buffer.c_str(), TAG_FILE); + dispatch_simple_text_command(buffer.c_str(), TAG_FILE); } } @@ -752,7 +767,7 @@ void dispatch_run_script(const char*, const char* payload, uint8_t source) std::string line; while(std::getline(f, line)) { LOG_VERBOSE(TAG_HASP, line.c_str()); - if(!line.empty() && line[0] != '#') dispatch_text_line(line.c_str(), TAG_FILE); // # for comments + if(!line.empty() && line[0] != '#') dispatch_simple_text_command(line.c_str(), TAG_FILE); // # for comments } } else { LOG_ERROR(TAG_MSGR, F(D_FILE_LOAD_FAILED), payload); @@ -841,17 +856,10 @@ void dispatch_page(const char*, const char* payload, uint8_t source) dispatch_set_page(pageid, animation, time, delay); } -// Clears all fonts -void dispatch_clear_font(const char*, const char* payload, uint8_t source) -{ - hasp_init(); - font_clear_list(payload); -} - // Clears a page id or the current page if empty void dispatch_clear_page(const char*, const char* page, uint8_t source) { - if(!strcasecmp_P(page, PSTR("all"))) { + if(!strcasecmp(page, "all")) { hasp_init(); return; } @@ -865,6 +873,14 @@ void dispatch_clear_page(const char*, const char* page, uint8_t source) haspPages.clear(pageid); } +// Clears all fonts +void dispatch_clear_font(const char*, const char* payload, uint8_t source) +{ + dispatch_clear_page(NULL, "all", source); + hasp_init(); + font_clear_list(payload); +} + void dispatch_dim(const char*, const char* level) { // Set the current state diff --git a/src/hasp/hasp_dispatch.h b/src/hasp/hasp_dispatch.h index 47ab678a..2e438a25 100644 --- a/src/hasp/hasp_dispatch.h +++ b/src/hasp/hasp_dispatch.h @@ -54,6 +54,7 @@ void dispatch_parse_jsonl(Stream& stream, uint8_t& saved_page_id); #else 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); void dispatch_clear_page(const char* page); void dispatch_json_error(uint8_t tag, DeserializationError& jsonError); diff --git a/src/hasp/hasp_event.cpp b/src/hasp/hasp_event.cpp index b2d71bd8..3a2f8602 100644 --- a/src/hasp/hasp_event.cpp +++ b/src/hasp/hasp_event.cpp @@ -86,6 +86,7 @@ void delete_event_handler(lv_obj_t* obj, lv_event_t event) my_obj_set_value_str_text(obj, part, LV_STATE_DISABLED + LV_STATE_CHECKED, NULL); } my_obj_set_tag(obj, (char*)NULL); + my_obj_set_action(obj, (char*)NULL); } /* ============================== Timer Event ============================ */ @@ -491,8 +492,31 @@ void generic_event_handler(lv_obj_t* obj, lv_event_t event) if(last_value_sent == HASP_EVENT_LOST) return; - /* If an actionid is attached, perform that action on UP event only */ - if(obj->user_data.actionid) { + if(obj->user_data.action) { + // if(last_value_sent == HASP_EVENT_UP || last_value_sent == HASP_EVENT_RELEASE) { + // dispatch_text_line(obj->user_data.action, TAG_EVENT); + + StaticJsonDocument<256> doc; + StaticJsonDocument<64> filter; + char eventname[8]; + + Parser::get_event_name(last_value_sent, eventname, sizeof(eventname)); + filter[eventname] = true; + DeserializationError jsonError = + deserializeJson(doc, (const char*)obj->user_data.action, DeserializationOption::Filter(filter)); + + if(!jsonError) { + JsonVariant json = doc[eventname].as(); + uint8_t savedPage = haspPages.get(); + if(!dispatch_json_variant(json, savedPage, TAG_EVENT)) { + LOG_WARNING(TAG_MSGR, F(D_DISPATCH_COMMAND_NOT_FOUND), eventname); + // dispatch_simple_text_command(payload, source); + } + } + // } + + } else if(obj->user_data.actionid) { + /* If an actionid is attached, perform that action on UP event only */ if(last_value_sent == HASP_EVENT_UP || last_value_sent == HASP_EVENT_RELEASE) { lv_scr_load_anim_t transitionid = (lv_scr_load_anim_t)obj->user_data.transitionid; switch(obj->user_data.actionid) {