diff --git a/components/nspanel_ha_blueprint/json.h b/components/nspanel_ha_blueprint/json.h new file mode 100644 index 0000000..0a27c50 --- /dev/null +++ b/components/nspanel_ha_blueprint/json.h @@ -0,0 +1,88 @@ +// json.h +#pragma once + +#include "cJSON.h" +#include +#include +#include + +// The `nspanel_ha_blueprint` namespace encapsulates utility functions for JSON manipulation +// tailored for the NSPanel Home Assistant Blueprint project. +namespace nspanel_ha_blueprint { + + /** + * Compares the string value of a cJSON object with a target C-string. + * + * This function checks if the given cJSON object is not NULL, is a string type, + * and its value matches the target string provided. + * + * @param json_item The cJSON object whose string value is to be compared. + * @param target_str The target C-string to compare against the cJSON object's value. + * @return True if the cJSON object's string value matches the target string, false otherwise. + */ + bool json_cmp_string(const cJSON* json_item, const char* target_str) { + if (json_item != NULL && cJSON_IsString(json_item) && json_item->valuestring != NULL) { + return strcmp(json_item->valuestring, target_str) == 0; + } + return false; + } + + /** + * Compares the integer value of a cJSON object with a target unsigned integer. + * + * This function checks if the given cJSON object is not NULL, represents a number, + * and its integer value matches the target unsigned integer provided. It's particularly + * useful for JSON fields that are expected to contain numeric boolean values (e.g., 0 or 1) + * or other unsigned integer values. + * + * @param json_item The cJSON object whose integer value is to be compared. + * @param target_uint The target unsigned integer to compare against the cJSON object's value. + * @return True if the cJSON object's integer value matches the target unsigned integer, false otherwise. + */ + bool json_cmp_uint(const cJSON* json_item, unsigned int target_uint) { + if (json_item != NULL && cJSON_IsNumber(json_item)) { + return (unsigned int)json_item->valueint == target_uint; + } + return false; + } + + /** + * Retrieves a floating-point value from a cJSON object. + * + * @param json_item The cJSON object from which to retrieve the value. + * @return The floating-point value of the cJSON object if it is a number, NaN otherwise. + */ + float json_get_float(const cJSON* json_item) { + if (json_item != NULL && cJSON_IsNumber(json_item)) { + return static_cast(json_item->valuedouble); + } + return std::nan(""); // Return NaN if not a valid number + } + + /** + * Converts a JSON integer percentage value to a string representing a floating-point number between 0.00 and 1.00. + * + * @param json_item The cJSON object containing the integer percentage value. + * @return A string representation of the floating-point number, or an empty string if the input is invalid. + */ + std::string json_percentage_to_float_string(const cJSON* json_item) { + // Ensure the item is a valid number + if (json_item != NULL && cJSON_IsNumber(json_item)) { + // Convert the integer percentage to a float between 0.00 and 1.00 + float percentage = static_cast(json_item->valueint) / 100.0f; + + // Buffer to hold the formatted string, large enough for "0.00" plus null terminator + char buffer[8]; + + // Format the floating-point number with two decimal places + snprintf(buffer, sizeof(buffer), "%.2f", percentage); + + // Return the formatted string + return std::string(buffer); + } + + // Return an empty string to indicate invalid input or non-numeric JSON item + return ""; + } + +} // namespace nspanel_ha_blueprint diff --git a/esphome/nspanel_esphome_core.yaml b/esphome/nspanel_esphome_core.yaml index db01649..67f048b 100644 --- a/esphome/nspanel_esphome_core.yaml +++ b/esphome/nspanel_esphome_core.yaml @@ -17,7 +17,7 @@ substitutions: temp_units: "°C" invalid_cooldown: "100ms" ##### DON'T CHANGE THIS ###### - version: "4.3.1" + version: "4.3.2d1" ############################## ##### External components ##### @@ -874,6 +874,18 @@ display: break; } break; + case 1: // Home + switch (component_id) { + case 4: // indr_temp + case 27: // indr_temp_icon + if (!touch_event) { // Release + detailed_entity->publish_state((id(is_embedded_thermostat)) ? "embedded_climate" : ""); + disp1->set_component_value("climate.embedded", id(is_embedded_thermostat) ? 1 : 0); + goto_page->execute("climate"); + } + break; + } + break; case 8: // Settings switch (component_id) { case 9: // Reboot button @@ -1672,44 +1684,6 @@ text_sensor: name: Notification Text platform: template - ##### NSPanel event sensor, the main action sensor - push to HA ##### - - id: disp1_nspanel_event - name: NSPanel event - platform: nextion - nextion_id: disp1 - component_name: nspanelevent - internal: true - filters: - - lambda: |- - x = x.c_str(); - x.shrink_to_fit(); - return x; - on_value: - then: - - lambda: |- - ESP_LOGE("text_sensor.nspanelevent", "Obsolete call"); - DynamicJsonDocument doc(1024); - deserializeJson(doc, x); - std::string page = doc["page"]; - std::string component = doc["component"]; - if (not (component == "currentpage" and (page == "screensaver" or page == "home"))) timer_reset_all->execute(); - std::string value = doc["value"]; - std::string entity = detailed_entity->state.c_str(); // doc["entity"]; - esphome::api::CustomAPIDevice ha_event; - ha_event.fire_homeassistant_event("esphome.nspanel_ha_blueprint", - { - {"device_name", device_name->state.c_str()}, - {"type", "generic"}, - {"page", page}, - {"component", component}, - {"value", value}, - {"entity", entity} - }); - ESP_LOGE("text_sensor.nspanelevent", " Page: %s", page.c_str()); - ESP_LOGE("text_sensor.nspanelevent", " Component: %s", component.c_str()); - ESP_LOGE("text_sensor.nspanelevent", " Value: %s", value.c_str()); - ESP_LOGE("text_sensor.nspanelevent", " Entity: %s", entity.c_str()); - ##### NSPanel event - Execute actions from ESPHome - NO push to HA ##### - id: disp1_local_event name: NSPanel local event @@ -1725,80 +1699,74 @@ text_sensor: on_value: then: - lambda: |- - DynamicJsonDocument doc(1024); - deserializeJson(doc, x); - std::string page = doc["page"]; - std::string event = doc["event"]; - std::string component = doc["component"]; - std::string key = doc["key"]; - std::string value = doc["value"]; - std::string entity = detailed_entity->state.c_str(); // doc["entity"]; - int embedded = doc["embedded"]; - std::string service = ""; + cJSON *json = cJSON_Parse(x.c_str()); + if (!json) { + ESP_LOGE("text_sensor.disp1_local_event", "Error parsing json: %s", x.c_str()); + } else { + const cJSON* page = cJSON_GetObjectItemCaseSensitive(json, "page"); + const cJSON* event = cJSON_GetObjectItemCaseSensitive(json, "event"); + const cJSON* component = cJSON_GetObjectItemCaseSensitive(json, "component"); + const cJSON* key = cJSON_GetObjectItemCaseSensitive(json, "key"); + const cJSON* value = cJSON_GetObjectItemCaseSensitive(json, "value"); + const cJSON* embedded = cJSON_GetObjectItemCaseSensitive(json, "embedded"); - // Send event to Home Assistant - if (event == "short_click" or event == "long_click") { - ha_button->execute(page.c_str(), component.c_str(), event.c_str()); - } else if (event == "click" and page == "home" and component == "climate") { - detailed_entity->publish_state((id(is_embedded_thermostat)) ? "embedded_climate" : ""); - disp1->set_component_value("climate.embedded", id(is_embedded_thermostat) ? 1 : 0); - goto_page->execute("climate"); - } else if (page == "light" or page == "climate") { // Generic event + // Send event to Home Assistant + if (json_cmp_string(event, "short_click") or json_cmp_string(event, "long_click")) { + ha_button->execute(page->valuestring, component->valuestring, event->valuestring); + } else if (json_cmp_string(page, "light") or json_cmp_string(page, "climate")) { // Generic event esphome::api::CustomAPIDevice ha_event; ha_event.fire_homeassistant_event("esphome.nspanel_ha_blueprint", { {"device_name", device_name->state.c_str()}, {"type", "generic"}, - {"page", page}, - {"event", event}, - {"value", value}, - {"entity", entity} + {"page", page->valuestring}, + {"component", component->valuestring}, + {"event", event->valuestring}, + {"value", value->valuestring}, + {"entity", detailed_entity->state.c_str()} }); } - // page based actions - if (page == "alarm") - { - std::string code_format = doc["code_format"]; - std::string code_arm_req = doc["code_arm_req"]; - std::string title = doc["mui"]; - if (code_format == "number" and (key == "disarm" or code_arm_req == "1")) - { - goto_page->execute("keyb_num"); - disp1->set_component_value("keyb_num.page_id", 23); //Calling from Alarm page - disp1->set_component_text("keyb_num.domain", page.c_str()); - disp1->set_component_text("keyb_num.key", key.c_str()); - disp1->set_component_text("keyb_num.value", value.c_str()); - disp1->set_component_text("keyb_num.entity", entity.c_str()); - disp1->set_component_text("keyb_num.title", title.c_str()); - } - else service_call_alarm_control_panel->execute(entity.c_str(), key.c_str(), code_format.c_str(), ""); + // page based actions + if (json_cmp_string(page, "alarm")) { + const cJSON* code_format = cJSON_GetObjectItemCaseSensitive(json, "code_format"); + const cJSON* code_arm_req = cJSON_GetObjectItemCaseSensitive(json, "code_arm_req"); + const cJSON* title = cJSON_GetObjectItemCaseSensitive(json, "mui"); + if (json_cmp_string(code_format, "number") and (json_cmp_string(key, "disarm") or json_cmp_string(code_arm_req, "1"))) { + goto_page->execute("keyb_num"); + disp1->set_component_value("keyb_num.page_id", 23); //Calling from Alarm page + disp1->set_component_text("keyb_num.domain", page->valuestring); + disp1->set_component_text("keyb_num.key", key->valuestring); + disp1->set_component_text("keyb_num.value", value->valuestring); + disp1->set_component_text("keyb_num.entity", detailed_entity->state.c_str()); + disp1->set_component_text("keyb_num.title", title->valuestring); + } else service_call_alarm_control_panel->execute(detailed_entity->state.c_str(), key->valuestring, code_format->valuestring, ""); + } else if (json_cmp_string(page, "climate")) { + change_climate_state->execute((embedded != NULL and cJSON_IsNumber(embedded) and embedded->valueint == 1), key->valuestring, value->valuestring); + } else if (json_cmp_string(page, "cover")) { + if (json_cmp_string(key, "position")) ha_call_service->execute("cover.set_cover_position", key->valuestring, value->valuestring, detailed_entity->state.c_str()); + else ha_call_service->execute((std::string("cover.") + key->valuestring), "", "", detailed_entity->state.c_str()); + } else if (json_cmp_string(page, "fan")) { + if (json_cmp_string(key, "stop") or json_cmp_string(value, "0") == 0) ha_call_service->execute("fan.turn_off", "", "", detailed_entity->state.c_str()); + else ha_call_service->execute("fan.turn_on", key->valuestring, value->valuestring, detailed_entity->state.c_str()); + } else if (json_cmp_string(page, "keyb_num") == 0) { + const cJSON* base_domain = cJSON_GetObjectItemCaseSensitive(json, "base_domain"); + if (json_cmp_string(base_domain, "alarm")) { + const cJSON* code_format = cJSON_GetObjectItemCaseSensitive(json, "code_format"); + const cJSON* pin = cJSON_GetObjectItemCaseSensitive(json, "pin"); + service_call_alarm_control_panel->execute(detailed_entity->state.c_str(), key->valuestring, code_format->valuestring, pin->valuestring); + } + goto_page->execute((base_domain != NULL && cJSON_IsString(base_domain)) ? base_domain->valuestring : "home"); } - else if (page == "climate") { - change_climate_state->execute((embedded==1), key.c_str(), value.c_str()); - } - else if (page == "cover") { - if (key == "position") ha_call_service->execute("cover.set_cover_position", key.c_str(), value.c_str(), entity.c_str()); - else ha_call_service->execute((std::string("cover.") + key.c_str()), "", "", entity.c_str()); - } - else if (page == "fan") { - if (key == "stop" or value == "0") ha_call_service->execute("fan.turn_off", "", "", entity.c_str()); - else ha_call_service->execute("fan.turn_on", key.c_str(), value.c_str(), entity.c_str()); - } - else if (page == "keyb_num") { - std::string base_domain = doc["base_domain"]; - if (base_domain == "alarm") { - std::string code_format = doc["code_format"]; - std::string pin = doc["pin"]; - service_call_alarm_control_panel->execute(entity.c_str(), key.c_str(), code_format.c_str(), pin.c_str()); + else if (json_cmp_string(page, "light")) ha_call_service->execute("light.turn_on", key->valuestring, value->valuestring, detailed_entity->state.c_str()); + else if (json_cmp_string(page, "media_player")) { + if (json_cmp_string(key, "volume_mute")) + ha_call_service->execute("media_player.volume_mute", "is_volume_muted", value->valuestring, detailed_entity->state.c_str()); + else if (json_cmp_string(key, "volume_set")) + ha_call_service->execute("media_player.volume_set", "volume_level", json_percentage_to_float_string(value).c_str()), detailed_entity->state.c_str()); + else if ((key != NULL and key->valuestring != NULL and key->valuestring[0] != '\0')) + ha_call_service->execute((std::string("media_player.") + key->valuestring), "", "", detailed_entity->state.c_str()); } - else if (base_domain == "" or base_domain.empty()) base_domain = "home"; - goto_page->execute(base_domain.c_str()); - } - else if (page == "light") ha_call_service->execute("light.turn_on", key.c_str(), value.c_str(), entity.c_str()); - else if (page == "media_player") { - if (key == "volume_mute") ha_call_service->execute("media_player.volume_mute", "is_volume_muted", value.c_str(), entity.c_str()); - else if (key == "volume_set") ha_call_service->execute("media_player.volume_set", "volume_level", to_string(stof(value) / 100), entity.c_str()); - else if (not key.empty()) ha_call_service->execute((std::string("media_player.") + key.c_str()), "", "", entity.c_str()); + cJSON_Delete(json); } ##### Versioning ##### diff --git a/hmi/dev/nspanel_CJK_eu_code/utilities2.txt b/hmi/dev/nspanel_CJK_eu_code/utilities2.txt deleted file mode 100644 index 351e7bc..0000000 --- a/hmi/dev/nspanel_CJK_eu_code/utilities2.txt +++ /dev/null @@ -1,440 +0,0 @@ -Page utilities2 - Attributes - ID : 0 - Scope : local - Dragging : 0 - Send Component ID : on press and release - Locked : no - Swide up page ID : disabled - Swide down page ID : disabled - Swide left page ID : disabled - Swide right page ID: disabled - - Events - Preinitialize Event - if(api==0) - { - page home - } - vis unavailable,0 - - Postinitialize Event - sendme - -Text title - Attributes - ID : 1 - Scope : local - Dragging : 0 - Send Component ID : on press and release - Associated Keyboard: none - Text : Power Dashboard - Max. Text Size : 100 - -Text title_icon - Attributes - ID : 2 - Scope : local - Dragging : 0 - Send Component ID : on press and release - Associated Keyboard: none - Text :  - Max. Text Size : 10 - -Text unavailable - Attributes - ID : 5 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : - Max. Text Size : 1 - -Text t1 - Attributes - ID : 6 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 賈 - Max. Text Size : 4 - -Text t2 - Attributes - ID : 7 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text :  - Max. Text Size : 4 - -Text t3 - Attributes - ID : 8 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text :  - Max. Text Size : 4 - -Text t4 - Attributes - ID : 9 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Green - Max. Text Size : 10 - -Text t5 - Attributes - ID : 10 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Fossil - Max. Text Size : 10 - -Text t7 - Attributes - ID : 13 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 全 - Max. Text Size : 4 - -Text t8 - Attributes - ID : 14 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Solar - Max. Text Size : 10 - -Text t9 - Attributes - ID : 16 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 98% - Max. Text Size : 10 - -Text t10 - Attributes - ID : 17 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 2% - Max. Text Size : 10 - -Text t11 - Attributes - ID : 18 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 豈 - Max. Text Size : 4 - -Text t12 - Attributes - ID : 19 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Battery - Max. Text Size : 10 - -Text t13 - Attributes - ID : 21 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 暑 - Max. Text Size : 4 - -Text t14 - Attributes - ID : 22 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Heating - Max. Text Size : 10 - -Text t15 - Attributes - ID : 24 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 1.7 kW - Max. Text Size : 10 - -Text t16 - Attributes - ID : 25 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 21.7°C - Max. Text Size : 10 - -Text t17 - Attributes - ID : 26 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 1.1 kW - Max. Text Size : 10 - -Text t18 - Attributes - ID : 27 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : -2.1 kW - Max. Text Size : 10 - -Text t19 - Attributes - ID : 28 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 0.8 kW - Max. Text Size : 10 - -Text t20 - Attributes - ID : 29 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 慎 - Max. Text Size : 4 - -Text t21 - Attributes - ID : 30 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Wind - Max. Text Size : 10 - -Text t22 - Attributes - ID : 32 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 1.1 kW - Max. Text Size : 10 - -Text t23 - Attributes - ID : 33 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : î—° - Max. Text Size : 4 - -Text t24 - Attributes - ID : 34 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Car - Max. Text Size : 10 - -Text t25 - Attributes - ID : 36 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 1.1 kW - Max. Text Size : 10 - -Text t0 - Attributes - ID : 37 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : î—° - Max. Text Size : 4 - -Text t6 - Attributes - ID : 38 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : Car - Max. Text Size : 10 - -Text t26 - Attributes - ID : 40 - Scope : local - Dragging : 0 - Send Component ID : disabled - Associated Keyboard: none - Text : 1.1 kW - Max. Text Size : 10 - -Slider h1 - Attributes - ID : 11 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Slider h2 - Attributes - ID : 12 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Slider h3 - Attributes - ID : 15 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Slider h4 - Attributes - ID : 20 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Slider h5 - Attributes - ID : 23 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Slider h6 - Attributes - ID : 31 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Slider h7 - Attributes - ID : 35 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Slider h0 - Attributes - ID : 39 - Scope : local - Dragging : 0 - Send Component ID: disabled - Position : 50 - Upper range limit: 100 - Lower range limit: 0 - -Button button_back - Attributes - ID : 3 - Scope : local - Dragging : 0 - Send Component ID: on press and release - State : unpressed - Text : î…˜ - Max. Text Size : 3 - - Events - Touch Press Event - page back_page_id - -Timer wakeup_timer - Attributes - ID : 4 - Scope : local - Period (ms): 100 - Enabled : yes - - Events - Timer Event - if(dim