From 4ad5457b0f0791aa3fff43f2f6c3e45bb2788bab Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:57:16 +0100 Subject: [PATCH] ESPHome Watchdog This implements a watchdog script with complementing the scripts `on_setup` and `refresh_wifi_icon` to be checking the components every minute and taking proper actions (when possible). Solves #1464 --- .../nspanel_esphome_addon_upload_tft.yaml | 40 +-- .../esphome/nspanel_esphome_advanced.yaml | 8 - advanced/esphome/nspanel_esphome_core.yaml | 275 +++++++++++------- advanced/hmi/nspanel_blank.HMI | Bin 7370184 -> 7370184 bytes advanced/hmi/nspanel_blank_code/Program.s.txt | 2 + advanced/hmi/nspanel_blank_code/blank.txt | 66 ++--- advanced/hmi/nspanel_eu.HMI | Bin 15033379 -> 15033379 bytes advanced/hmi/nspanel_eu_code/Program.s.txt | 2 +- advanced/hmi/nspanel_eu_code/boot.txt | 140 +++------ advanced/hmi/nspanel_us.HMI | Bin 14820802 -> 14820802 bytes advanced/hmi/nspanel_us_code/Program.s.txt | 2 +- advanced/hmi/nspanel_us_code/boot.txt | 137 +++------ nspanel_blank.tft | Bin 275296 -> 274936 bytes 13 files changed, 302 insertions(+), 370 deletions(-) diff --git a/advanced/esphome/nspanel_esphome_addon_upload_tft.yaml b/advanced/esphome/nspanel_esphome_addon_upload_tft.yaml index 9bc9f44..fcb5b39 100644 --- a/advanced/esphome/nspanel_esphome_addon_upload_tft.yaml +++ b/advanced/esphome/nspanel_esphome_addon_upload_tft.yaml @@ -71,6 +71,8 @@ script: - lambda: |- static const char *const TAG = "script.upload_tft_new"; ESP_LOGVV(TAG, "Starting..."); + id(is_uploading_tft) = true; + nextion_status->execute(); auto delay_seconds_ = [](int seconds) { @@ -187,6 +189,7 @@ script: delay_seconds_(2); App.safe_reboot(); + id(is_uploading_tft) = false; ESP_LOGD(TAG, "Finished!"); - id: upload_tft @@ -197,6 +200,8 @@ script: - lambda: |- static const char *const TAG = "script.upload_tft"; ESP_LOGD(TAG, "Starting..."); + id(is_uploading_tft) = true; + char update_msg[128]; auto delay_seconds_ = [](int seconds) { @@ -226,9 +231,9 @@ script: auto send_nextion_command = [](const std::string &command) -> bool { static const char *const TAG = "script.upload_tft.send_nextion_command"; ESP_LOGD(TAG, "Sending: %s", command.c_str()); - id(tf_uart).write_str(command.c_str()); + tf_uart->write_str(command.c_str()); const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; - id(tf_uart).write_array(to_send, sizeof(to_send)); + tf_uart->write_array(to_send, sizeof(to_send)); return true; }; @@ -285,15 +290,15 @@ script: start = millis(); - while ((timeout == 0 && id(tf_uart).available()) || millis() - start <= timeout) + while ((timeout == 0 && tf_uart->available()) || millis() - start <= timeout) { - if (!id(tf_uart).available()) + if (!tf_uart->available()) { App.feed_wdt(); continue; } - id(tf_uart).read_byte(&c); + tf_uart->read_byte(&c); if (c == 0xFF) { nr_of_ff_bytes++; @@ -424,7 +429,7 @@ script: for (int i = 0; i < range; i += 4096) { App.feed_wdt(); write_len = content_length_ < 4096 ? content_length_ : 4096; - id(tf_uart).write_array(&transfer_buffer_[i], write_len); + tf_uart->write_array(&transfer_buffer_[i], write_len); content_length_ -= write_len; ESP_LOGD(TAG, "Uploaded %0.1f %%, remaining %d bytes, heap: %d", 100.0 * (tft_size_ - content_length_) / tft_size_, @@ -543,19 +548,19 @@ script: // If it fails for any reason a power cycle of the display will be needed sprintf(command, "whmi-wris %d,%d,1", content_length_, update_baud_rate_); - ESP_LOGD(TAG, "Clear serial receive buffer: %d", id(tf_uart).available()); + ESP_LOGD(TAG, "Clear serial receive buffer: %d", tf_uart->available()); // Clear serial receive buffer uint8_t d; - while (id(tf_uart).available()) { - id(tf_uart).read_byte(&d); + while (tf_uart->available()) { + tf_uart->read_byte(&d); }; send_nextion_command(command); - if (update_baud_rate_ != id(tf_uart).get_baud_rate()) { + if (update_baud_rate_ != tf_uart->get_baud_rate()) { set_baud_rate_(update_baud_rate_); - //id(tf_uart).set_baud_rate(update_baud_rate_); - //id(tf_uart).setup(); + //tf_uart->set_baud_rate(update_baud_rate_); + //tf_uart->setup(); //delay_seconds_(2); } @@ -809,16 +814,16 @@ script: // Clear serial receive buffer uint8_t d; - while (id(tf_uart).available()) { - id(tf_uart).read_byte(&d); + while (tf_uart->available()) { + tf_uart->read_byte(&d); }; send_nextion_command(command); - if (update_baud_rate_ != id(tf_uart).get_baud_rate()) { + if (update_baud_rate_ != tf_uart->get_baud_rate()) { set_baud_rate_(update_baud_rate_); - //id(tf_uart).set_baud_rate(update_baud_rate_); - //id(tf_uart).setup(); + //tf_uart->set_baud_rate(update_baud_rate_); + //tf_uart->setup(); } std::string response; @@ -1019,6 +1024,7 @@ script: delay_seconds_(2); App.safe_reboot(); + id(is_uploading_tft) = false; ESP_LOGD(TAG, "Finished!"); select: diff --git a/advanced/esphome/nspanel_esphome_advanced.yaml b/advanced/esphome/nspanel_esphome_advanced.yaml index ec56498..fd7940e 100644 --- a/advanced/esphome/nspanel_esphome_advanced.yaml +++ b/advanced/esphome/nspanel_esphome_advanced.yaml @@ -59,14 +59,6 @@ sensor: accuracy_decimals: 0 update_interval: never - ##### WIFI Signal stregth - - name: ${device_name} RSSI - platform: wifi_signal - update_interval: 60s - on_value: - - script.execute: - id: refresh_wifi_icon - text_sensor: ##### ESPhome version used to compile the app ##### - name: ${device_name} ESPhome Version diff --git a/advanced/esphome/nspanel_esphome_core.yaml b/advanced/esphome/nspanel_esphome_core.yaml index 50a795f..db7f558 100644 --- a/advanced/esphome/nspanel_esphome_core.yaml +++ b/advanced/esphome/nspanel_esphome_core.yaml @@ -104,6 +104,12 @@ wifi: ap: ssid: "${device_name}" password: ${wifi_password} + on_connect: + then: + - script.execute: watchdog + on_disconnect: + then: + - script.execute: watchdog ##### OTA PASSWORD ##### ota: @@ -148,8 +154,9 @@ time: on_time: - seconds: 0 then: - - script.execute: - id: refresh_datetime + - script.execute: refresh_datetime + - script.execute: watchdog + on_time_sync: then: - logger.log: "System clock synchronized" @@ -160,6 +167,10 @@ time: api: id: api_server reboot_timeout: 0s + on_client_connected: + - script.execute: watchdog + on_client_disconnected: + - script.execute: watchdog services: ##### Service to send a command "printf" directly to the display ##### @@ -364,7 +375,6 @@ api: - lambda: |- ESP_LOGV("service.notification_show", "Starting"); - disp1->send_command_printf("is_notification=1"); disp1->goto_page("notification"); disp1->set_component_text_printf("notification.notifi_label", "%s", label.c_str()); @@ -373,13 +383,13 @@ api: notification_label->publish_state(label.c_str()); notification_text->publish_state(message.c_str()); timer_reset_all->execute(current_page->state.c_str()); + refresh_notification->execute(); notification_unread->turn_on(); if (notification_sound->state) buzzer->play("two short:d=4,o=5,b=100:16e6,16e6"); ##### Service to clear the notification ##### - service: notification_clear then: - - logger.log: "Service: notification_clear" - script.execute: notification_clear ##### Service to open information for settings-page(s) @@ -427,7 +437,6 @@ api: then: - lambda: |- if (current_page->state == "climate") detailed_entity->publish_state(entity); - - script.execute: id: set_climate current_temp: !lambda "return current_temp;" @@ -731,6 +740,18 @@ display: ##### START - GLOBALS CONFIGURATION ##### globals: + ##### Is uploading TFT ##### + - id: is_uploading_tft + type: bool + restore_value: false + initial_value: 'false' + + ##### Is blueprint updated ##### + - id: is_blueprint_updated + type: bool + restore_value: false + initial_value: 'false' + ##### Is boot sequence completed? ##### - id: setup_sequence_completed type: bool @@ -1027,8 +1048,7 @@ binary_sensor: id: api_status on_state: then: - - script.execute: - id: refresh_wifi_icon + - script.execute: watchdog ##### START - BUTTON CONFIGURATION ##### button: @@ -1268,8 +1288,8 @@ sensor: - lambda: |- timer_reset_all->execute("settings"); - - id: page_id - name: ${device_name} Page Id + - name: ${device_name} Page Id + id: page_id platform: nextion variable_name: dp precision: 0 @@ -1284,8 +1304,8 @@ sensor: } ##### Display mode (1 = EU, 2 = US, 3 = US Landscape) - - id: display_mode - name: ${device_name} Display mode + - name: ${device_name} Display mode + id: display_mode platform: nextion variable_name: display_mode precision: 0 @@ -1293,14 +1313,22 @@ sensor: entity_category: diagnostic ##### Charset (1 = International (original), 2 = CJK languages) - - id: display_charset - name: ${device_name} Display charset + - name: ${device_name} Display charset + id: display_charset platform: nextion variable_name: charset precision: 0 internal: true entity_category: diagnostic + ##### WIFI Signal stregth + - name: ${device_name} RSSI + id: wifi_rssi + platform: wifi_signal + internal: false + disabled_by_default: true + entity_category: diagnostic + ##### START - SWITCH CONFIGURATION ##### switch: @@ -1373,7 +1401,7 @@ switch: timeout: 20s - lambda: |- if (id(setup_sequence_completed)) { - nextion_init->publish_state(true); + nextion_init->publish_state(disp1->is_setup()); disp1->goto_page(wakeup_page_name->state.c_str()); } on_turn_off: @@ -1497,7 +1525,6 @@ text_sensor: std::string service = ""; // Send event to Home Assistant - auto ha_event = new esphome::api::CustomAPIDevice(); 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") { @@ -1505,6 +1532,7 @@ text_sensor: disp1->set_component_value("climate.embedded", id(is_embedded_thermostat) ? 1 : 0); disp1->goto_page("climate"); } else if (page == "light" or page == "climate" or page == "notification") { // Generic event + auto ha_event = new esphome::api::CustomAPIDevice(); ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint", { {"type", "generic"}, {"page", page}, @@ -1532,29 +1560,6 @@ text_sensor: } else service_call_alarm_control_panel->execute(entity.c_str(), key.c_str(), code_format.c_str(), ""); } - else if (page == "blank") page_blank->execute(); - else if (page == "boot") - { - // Contruct page boot - page_boot->execute(); - - // Detect TFT version - version_tft->update(); - check_versions->execute(); - - // Detect timeout - if (event == "timeout") - { - ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint", - { - {"type", "boot"}, - {"step", "timeout"}, - {"value", value} - }); - if (stof(value) >= 5) - disp1->goto_page(wakeup_page_name->state.c_str()); - } - } else if (page == "climate") { if (embedded==1) addon_climate_service_call->execute(key.c_str(), value.c_str()); @@ -1596,7 +1601,7 @@ text_sensor: - id: version_tft name: ${device_name} TFT version platform: nextion - component_name: boot.tft_version + component_name: tft_version entity_category: diagnostic internal: true on_value: @@ -1605,7 +1610,6 @@ text_sensor: ESP_LOGD(TAG, "TFT version: %s", version_tft->state.c_str()); if (current_page->state == "boot") { disp1->send_command_printf("tm_esphome.en=0"); - disp1->send_command_printf("tm_pageid.en=0"); page_boot->execute(); timer_reset_all->execute("boot"); } @@ -1731,30 +1735,17 @@ script: mode: restart then: - logger.log: "Exit reparse" - - uart.write: - id: tf_uart - data: "DRAKJHSUYDGBNCJHGJKSHBDN" - - uart.write: - id: tf_uart - data: [0xFF, 0xFF, 0xFF] - - uart.write: - id: tf_uart - data: "recmod=0" - - uart.write: - id: tf_uart - data: [0xFF, 0xFF, 0xFF] - - uart.write: - id: tf_uart - data: "recmod=0" - - uart.write: - id: tf_uart - data: [0xFF, 0xFF, 0xFF] - - uart.write: - id: tf_uart - data: "connect" - - uart.write: - id: tf_uart - data: [0xFF, 0xFF, 0xFF] + - uart.write: "DRAKJHSUYDGBNCJHGJKSHBDN" + - uart.write: [0xFF, 0xFF, 0xFF] + - uart.write: "connect" + - uart.write: [0xFF, 0xFF, 0xFF] + - uart.write: [0xFF, 0xFF] + - uart.write: "connect" + - uart.write: [0xFF, 0xFF, 0xFF] + - uart.write: "recmod=0" + - uart.write: [0xFF, 0xFF, 0xFF] + - uart.write: "recmod=0" + - uart.write: [0xFF, 0xFF, 0xFF] - id: global_settings mode: restart @@ -1836,6 +1827,7 @@ script: timer_reset_all->execute(wakeup_page_name->state.c_str()); - lambda: |- + id(is_blueprint_updated) = true; ESP_LOGV("script.global_settings", "Finished"); - id: ha_button @@ -1933,12 +1925,11 @@ script: mode: restart then: - lambda: |- - disp1->send_command_printf("is_notification=0"); if (current_page->state == "notification") disp1->goto_page("home"); notification_label->publish_state(""); notification_text->publish_state(""); notification_unread->turn_off(); - if (current_page->state == "home") disp1->hide_component("bt_notific"); + refresh_notification->execute(); - id: open_entity_settings_page mode: restart @@ -1987,7 +1978,6 @@ script: disp1->set_component_text_printf("esp_version", "ESP: ${version}"); // ESPHome version disp1->set_component_text_printf("framework", framework.c_str()); // ESPHome framework disp1->send_command_printf("tm_esphome.en=0"); - disp1->send_command_printf("tm_pageid.en=0"); - id: page_boot mode: restart @@ -2161,16 +2151,8 @@ script: - id: page_home mode: restart then: - - lambda: |- - static const char *const TAG = "script.page_home"; - if (current_page->state == "home") { // Is home page visible? - ESP_LOGV(TAG, "Update home page"); - refresh_relays->execute(); - refresh_wifi_icon->execute(); - disp1->send_command_printf("is_notification=%i", (notification_text->state.empty() and notification_label->state.empty()) ? 0 : 1); - set_component_color->execute("home.bt_notific", notification_unread->state ? id(home_notify_icon_color_unread) : id(home_notify_icon_color_normal)); - refresh_datetime->execute(); - } + - script.execute: refresh_relays + - script.execute: refresh_datetime - id: page_keyb_num mode: restart @@ -2280,6 +2262,21 @@ script: } else disp1->set_component_text_printf("home.meridiem", " "); disp1->set_component_text_printf("home.time", "%s", id(time_provider).now().strftime(time_format_str).c_str()); + - id: refresh_notification + mode: restart + then: + - lambda: |- + bool is_notification = (notification_text->state.empty() and notification_label->state.empty()); + disp1->send_command_printf("is_notification=%i", is_notification ? 0 : 1); + set_component_color->execute("home.bt_notific", notification_unread->state ? id(home_notify_icon_color_unread) : id(home_notify_icon_color_normal)); + if (current_page->state == "home") { + if (is_notification) { + disp1->show_component("bt_notific"); + } else { + disp1->hide_component("bt_notific"); + } + } + - id: refresh_relays mode: restart then: @@ -2297,30 +2294,17 @@ script: mode: restart then: - lambda: |- - static const char *const TAG = "script.refresh_wifi_icon"; - bool wifi_connected = wifi_component->is_connected(); - bool api_connected = api_server->is_connected(); - bool blueprint_connected = (not id(version_blueprint).empty()); - uint8_t api_val = (wifi_connected and api_connected and blueprint_connected) ? 1 : 0; - ESP_LOGV(TAG, "Wifi: %s", wifi_connected ? "Connected" : "DISCONNECTED"); - ESP_LOGV(TAG, "API: %s", api_connected ? "Connected" : "DISCONNECTED"); - ESP_LOGV(TAG, "Blueprint: %s", blueprint_connected ? id(version_blueprint).c_str() : "DISCONNECTED"); - ESP_LOGV(TAG, "Init: %s", nextion_init->state ? "True" : "False"); - ESP_LOGV(TAG, "Nextion api: %i", api_val); - if (nextion_init->state) { - // Update api value on Nextion - disp1->send_command_printf("api=%i", api_val); // Update Wi-Fi icon color - disp1->set_component_font_color("home.wifi_icon", (api_val > 0) ? 33808 : 63488); + disp1->set_component_font_color("home.wifi_icon", (id(is_blueprint_updated)) ? 33808 : 63488); // Update Wi-Fi icon disp1->set_component_text_printf("home.wifi_icon", "%s", - wifi_connected ? - (api_connected ? - (blueprint_connected ? "\uE5A8" : // mdi:wifi - All right! - "\uE7CF") : // mdi:home-assistant - Blueprint is out - "\uF256") : // mdi:api-off - "\uE5A9"); // mdi:wifi-off + wifi_component->is_connected() ? + (api_server->is_connected() ? + (id(is_blueprint_updated) ? "\uE5A8" : // mdi:wifi - All right! + "\uE7CF") : // mdi:home-assistant - Blueprint is out + "\uF256") : // mdi:api-off + "\uE5A9"); // mdi:wifi-off } - id: relay_settings @@ -2597,8 +2581,8 @@ script: disp1->send_command_printf("settings.dimslider.val=%i", id(display_dim_brightness_global)); disp1->send_command_printf("brightness_sleep=%i", int(display_sleep_brightness->state)); ESP_LOGD(TAG, "Report to Home Assistant"); - nextion_init->publish_state(true); - if (api_server->is_connected()) { + nextion_init->publish_state(disp1->is_setup()); + if (api_server->is_connected() and disp1->is_setup()) { auto ha_event = new esphome::api::CustomAPIDevice(); ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint", { @@ -2702,6 +2686,7 @@ script: timer_sleep->stop(); update_alarm_icon->stop(); update_climate_icon->stop(); + watchdog->stop(); ESP_LOGD(TAG, "Finished"); ###### Timers ###### @@ -2917,3 +2902,95 @@ script: disp1->set_component_font_color(component.c_str(), 1530); // cyan break; } + + - id: watchdog + mode: restart + then: + - lambda: |- + static const char *const TAG = "script.watchdog"; + ESP_LOGV(TAG, "Starting"); + if (id(is_uploading_tft)) { + ESP_LOGW(TAG, "TFT upload in progress"); + } else { + // report Wi-Fi status + bool wifi_connected = wifi_component->is_connected(); + if (wifi_connected) + ESP_LOGD(TAG, "Wi-Fi: %.0fdB", wifi_rssi->state); + else + ESP_LOGW(TAG, "Wi-Fi: DISCONNECTED"); + + // report API status + bool api_connected = api_server->is_connected(); + if (api_connected) + ESP_LOGD(TAG, "API: Connected"); + else + ESP_LOGW(TAG, "API: DISCONNECTED"); + + // Report blueprint version + id(is_blueprint_updated) = id(is_blueprint_updated) and wifi_connected and api_connected and (not id(version_blueprint).empty()); + if (id(is_blueprint_updated)) + ESP_LOGD(TAG, "Blueprint: %s", id(version_blueprint).c_str()); + else + ESP_LOGW(TAG, "Blueprint: %s", (wifi_connected and api_connected) ? "Pending" : "DISCONNECTED"); + + refresh_wifi_icon->execute(); + + // Report ESPHome + ESP_LOGD(TAG, "ESPHome:"); + ESP_LOGD(TAG, " Version: ${version}"); + // Report framework + #ifdef ARDUINO + ESP_LOGD(TAG, " Framework: arduino"); + #elif defined(USE_ESP_IDF) + ESP_LOGD(TAG, " Framework: esp-idf"); + #endif + + // Report UART + ESP_LOGD(TAG, "UART queue: %d", tf_uart->available()); + + // Report Nextion status + nextion_init->publish_state(nextion_init->state and disp1->is_setup()); + ESP_LOGD(TAG, "Nextion:"); + if (disp1->is_setup()) + ESP_LOGD(TAG, " Is setup: True"); + else if (disp1->is_detected()) + ESP_LOGW(TAG, " Is detected: %s", disp1->is_detected() ? "True" : "False"); + if (nextion_init->state) + ESP_LOGD(TAG, " Init: True"); + else + ESP_LOGW(TAG, " Init: False"); + if (version_tft->state.empty()) + ESP_LOGW(TAG, " TFT: UNKNOWN"); + else + ESP_LOGD(TAG, " TFT: %s", version_tft->state.c_str()); + // Update api value on Nextion + disp1->send_command_printf("api=%i", id(is_blueprint_updated) ? 1 : 0); + + if (!wifi_connected) { + ESP_LOGD(TAG, "Retry Wi-Fi connection"); + wifi_component->retry_connect(); + } + if (api_connected) { + if (not id(is_blueprint_updated)) { + ESP_LOGD(TAG, "Requesting blueprint settings"); + auto ha_event = new esphome::api::CustomAPIDevice(); + ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint", + { + {"type", "boot"}, + {"step", "timeout"} + }); + } + } else { + id(is_blueprint_updated) = false; + if (current_page->state != "blank" and + current_page->state != "boot" and + current_page->state != "home" and + current_page->state != "screensaver" and + current_page->state != "settings" and + current_page->state != "qrcode") { + ESP_LOGD(TAG, "Fallback to page Home"); + disp1->goto_page("home"); + } + } + } + ESP_LOGV(TAG, "Finished"); diff --git a/advanced/hmi/nspanel_blank.HMI b/advanced/hmi/nspanel_blank.HMI index b6252cd03875f2d51e4ab89fa4c5d0f1b6a82ae0..39fa3157f8016717f6daadfbb9ba432f6a6e734d 100644 GIT binary patch delta 3148 zcmeH{O-x)>6oBu0zziJ=VnJGB2A3>+jT>FK(1j)?wZ=4Y;RZc-c$0b1g(0$F z(VN`4=ic-4&Yb(*Gkg83vRRtBwTy-yO9uA!>=)v~05(p7^etzRbZR)QCjyqO8E3#| zB-;G>Nkw9b-(GpakCC64SIMTP<(e#F&5&-G{Ddnj#q^6U+gc9nQ=udWw)vVYWHH40 za|N(VBuhW?$@hD5(@nNn0Q7>5{quDzuq(DsU7~w23hV~yM9z%zDaf=HYt{l5l_};7 zXJ;CJJ73KCGU-~ejp0X&F3_&6TI2tI+m7{$Zbhez;9d3m*AD2STdtnW0tC^RURQ5F$?1QSbrjAm|9rbu|o+en$q3p zS3nnvtzo6za56QTsyr`F+QiY!vzyMpV<2+~w-OKEmqU$b9bQ&gQ&RQ2xS##(gC zetzjF)bXEoLPIb!l<6Na(^f2%TMbYMh}`+W3o>0o43 zah&?~>}W8M8O``rzq*+WObd!16USO}cQn$e^qQIUn6k0iS2U*^V1m$1R0UC}*wB^& zu$sD66!ALd?88wM|*Y=y8?SMV+u zDK2)si(fwsTWnj`@og_RTv-NbQ#NTH`kzJ8W*nr&9HiarAZ^w`+PH(XiQA+thqPLo zG|fTnrbW=zY9;DcD|DBu=d2scfbOadUDyHL?+)l*bwGE(0o_dpbniK!`*=m5(?V^x zbQMZOyne^+>}s={fM}VDk~Xc3=m}?6O=n!mM)==&-a@Cm>Utu#Dq%aX@7jb+p#z8cf9ISz-#5QG zGk5!LTkn!*r!dCCYAE6^E?Gg+Fko?Ab@DOQaW=5vv@VS{1*2-%9n+J#abPh$@$4XD z?{6mWNqJ}KUsEV%0rrt{{5j=@ZP1XU_w0;hru32O(yN|6=)6=?+wLOS+s&98RO<1= zPR{}+T$Jm%%;aG?rE(z0?sTx1DYiS9gp!Q0YzxRtlHa@{TDR`MkB zWO7@gmD>+}J*n|2|C#*ExEX1WhAo8bQ;z9lrhx1y?lw$&Pc-i*s=v z&PN|Ez=c?di*PX(;SyYm%Wyds;|g4ft8g`z;2KY zZo#csgSDt)9d1KE2CyE3xE({d0~@dr!x+J)QxH#``Rv@4TADDvD93NLic$+iyW((M ze6v7}xWVD5;5m>Z##O;tK3nui@Ip$aa(^i7lZ7@GT%y|nO6m+}`T3vQtaA>21A`Mk zxK_Ga;H0Zg-Q|z@6r~_vmhX?wILFW&AF zC(6NdD40ZNAnT*6*E*mljbN-r|C-6pNQ^}dokstNYer3PDq|N79Z4UG=VlJY?>mR$ zN3t2~HTtX`&zLlz6Gau!Xb}Yxr11iA%Lh)eRf4S1qiAc0h9b=|*Mz!8HPRsVJ7A*2 zPGKFGB8%G+c=&GdQG#rdIN}szl*5*lQDW zvml>KLY@d2QggTl)LeuK!qI3&nihDj7`H%9y>EI^%38FvU{+rC-9DBR}Kl8ts Nd19^%P5hrQ{|-|E560) { - bauds=921600 - }else + rest + } + sendme + printh 91 + prints "display_mode",0 + printh 00 + prints display_mode,0 + printh FF FF FF + printh 92 + prints "tft_version",0 + printh 00 + prints tft_version.txt,0 + printh 00 + printh FF FF FF + covx counter.val,aux1.txt,0,0 + esp_version.txt="ESP: Retry #"+aux1.txt + sys0=counter.val%10 + if(sys0==0) { - bauds=115200 + baud=115200 } covx baud,baud_rate.txt,0,0 baud_rate.txt+=" bps" -Timer tm_pageid - Attributes - ID : 9 - Scope : local - Period (ms): 2500 - Enabled : yes - - Events - Timer Event - nspanelevent.txt="{\"page\": \"blank\", \"event\": \"pagechanged\", \"version\": \""+tft_version.txt+"\"}" - printh 92 - prints "localevent",0 - printh 00 - prints nspanelevent.txt,0 - printh 00 - printh FF FF FF - diff --git a/advanced/hmi/nspanel_eu.HMI b/advanced/hmi/nspanel_eu.HMI index aa595e92099a5761d1188b1d6dc011155b50841b..d3309d2e8a1d231bba5bec290b2ba73773d577aa 100644 GIT binary patch delta 2110 zcmeIxZ%`Cv90%}cIsO@emUu$gaN?Lc&~XQjAaE$6`6tQ$WvSuG9ddEpZMVnW5)z#J z5Hr)mOP|uz)XLOM?aU3*;!Mdhqm=$A2*sh9aU(kBCkn!eWf{WKg&y}>kG0MyvB9k~M`U|rk$ZE6I;+EF zoKrDZJH(coMb;gM(Li}Tn;`3=8AJ6ytw3bEkcFOHA$(5g)1|XpX3v`>MJt8SiMZ

HUG5}T5Ub2An`7T**b>Ps3CDoMgEgl8oU zPl`3I`h$l`^l7n)=XdK`1N!!WUVs?TK`g{Uyb{nSi2eUJA)&dyHK=b7qDw(^DTpow z(WM}|6hxPTYM1^C1>vARB2pnur6MY(60%S!O{3{lMl-0KX3{LWk7m;xnoINOewt4e z^Z+fOg|vtk(-K-r%jiLRh?djC^a!n>m9&Z;rAk^&R$4<Z(;P>P5Gp;2rLc<8Gt0t36yG!sFmk!Ua<1qU4qd z7k^e?*FwC-BY0Ur&Tj5TtkB^HTQa_?4O$K*FxH~wkx7hkEgw!{?5viZcQV$b<^EBO zt=DpGI%92G9vH(|tCl4LW9zj1>$vFLqUeR46BxS<-;TQZCgL;FvVRg|>Dq)~4r2{k z9%e%3`--JvZrT}1RZg#$vtq_!m8{BbvrA65)pl=TJcg3pWvjE}R+!CEXEB?0w8^Qd z|8ihHpT0X%s`PJ=|H=^ZmEZFHt^~e%mM@<-m-US1cO8{~7jEZc%lxCcahm@WAJ`Pg zOu?# zk6rSr!={_-;tLM?(;lIXNHRMz31vT{trP$POAE^m#&WJ0$G2a;iESq_g_{Hd8k zN~fCD1w`jbhODX@t4FePcYa_@;$Zd;PMJ4!N}hhm6mPY+(r5Qb4!7&pMWo#<8&buE ztI|Du?h$$HN<3p`m&BSICope#bW73Z>9W=p{&I+1)Jb$*bL9W7T5|w=zwhyf@fhn>;MkW!Sk>acEJm<8}`6ncoFu&emDRx z!OQRp9E4Zl5FCah@ERP2*Wnl(hZFDygyAH-38z4Tx1bZc;B7b!XW$)p7rG$=XW>0K z2j`&&-iHhD0elD-;Ul;NA44yE0-wTVxB`9f8GH_3z?bk9d=1~gw{R7{gYV%Q`~W|~ Mb)`4*)6)Ha1IL;X?f?J) delta 1656 zcmd_p`%e^C6bJA#EVC;T>!RHeO4>?dV;Wa}AZef?C~AvnDOk0oYK5f_>jQUWbhe-r zD5qUteJl&!K5Ur^q7`4b=#<*W+5!c^7p1knOR+zg{$iRmQR5GO7pH0CUtrRcd}dDW zJ@?M!;${)~#`k%+uUrK;47Tspg+LLJh8EurL|o##BR4ew0BR(U9ql2y;} zV(ZbrQKReWSRc*{_1Y_K=B6@38KIT(IA7}3@8rowV-h?F5%VeZByIbCeCPB zHgN|Yvx#0seZcG=t4)_gLcE$J!Y-}ZA4*kW4V0RpRPE7uql(83%!nM^G@QdY;dSei zD*KS$j=}@cZ6hj!NMt#P%B}q9NTOQ%-SONwBAYdy{~*x_>F#)e>)-nRcp`^2elCG% zq?HdngxY`dM53TfGly5}NkqpYml%;ck*MCv``kos>%%uvh#s+iRC$8vmUW?>Q^a6X zRi>yau8tOewD_Fnj>-DXc+pWsvnrB9M##9)h05e-W#y<&W}UM})FeJ)Xy z+2}BjWa~aziWPCSx=S3)*6n6auD(+i+k>5nX3TuOTo$22npNCTppUkjD<-b3h%(i* zu0qN03~R6!)`1Q_cmv9y9M;1IsDL-25~`pY-hvuncpKgUKWv0euovI&60) { - bauds=921600 - }else + rest + } + sendme + printh 91 + prints "display_mode",0 + printh 00 + prints display_mode,0 + printh FF FF FF + printh 91 + prints "charset",0 + printh 00 + prints charset,0 + printh FF FF FF + printh 92 + prints "tft_version",0 + printh 00 + prints tft_version.txt,0 + printh 00 + printh FF FF FF + covx counter.val,aux1.txt,0,0 + esph_version.txt="Retry #"+aux1.txt + sys0=counter.val%10 + if(sys0==0) { - bauds=115200 + baud=115200 } covx baud,baud_rate.txt,0,0 baud_rate.txt+=" bps" -Timer tm_pageid - Attributes - ID : 19 - Scope : local - Period (ms): 2500 - Enabled : yes - - Events - Timer Event - covx display_mode,aux2.txt,0,0 - covx charset,aux3.txt,0,0 - nspanelevent.txt="{\"page\": \"boot\", \"event\": \"pagechanged\", \"version\": \""+tft_version.txt+"\", \"display_mode\": \""+aux2.txt+"\", \"charset\": \""+aux3.txt+"\"}" - printh 92 - prints "localevent",0 - printh 00 - prints nspanelevent.txt,0 - printh 00 - printh FF FF FF - diff --git a/advanced/hmi/nspanel_us.HMI b/advanced/hmi/nspanel_us.HMI index ac31742a84208ec7e9edfa589e4440c0f3dd19f4..432b915d16e1a92ecc3bc6e3b639ff37c970b740 100644 GIT binary patch delta 3229 zcmeIy`%_e97zgn8EDJkoAR4r;U1nLr+Y;>x-a;spw7hPXN(So6u0jI4?%6%M$_ax< zP%{;U-Yyzw*%ed6+{z2E*RMPkJ~lii+}CXDks(xAt!Zw1I3e^`vr7uCaPvtbSFO7& zvDk>28#2NJm3GS&iJ2mH)TXYgwCz7jY=pWh>nw&S1TgK4>1Wz*N#aB?=B zoDC;u!^zojayFctO|RQ|TO>5w^n<-Ll19-Zlu4s0i?ZoadW>>t42`8+dYs15czS}K zqzN>UCedV?LQ}~`Pti1bnx3KQG=pYR9z9F5=sC)#0-8;8XfD}l9u<;|O(NgCN?ym~`z-D~ zKHVA{TEbW6b$Zeydx_bMr)S0U2Nk+*{gyr4GFaLAGG%?E(c=`o%B|eB9e!hSlOe-V zEV@hV{yC-Cu_@Kk137L_ahbQsXtCfuhHpe;WLhd;v|dga8<9H7s%#)MPBfYdi|iiJ z>FqvSd{>H^5uRv5B=&B=X>W!PLEjZD(gO3zpk{1EN-t92XJGhoRp6|q=|mfk`?jlQmhu`4DmigJz46DR9?E#IM42L z@a3!JWkMWJI@6dK*c9|xqIl=Ir5W6x-joo?KjO1S@eWB$=BEqgF??r#X&;Y2d%!I8 zQBL&)?;y@4|cV zK70TlLKAF}U_Tsy7HEYw2tqr20SDm_9EKzC yB^-rga2!s+NjL?k;VbwW&cHWt7QTh=;2eAp=ivwV5q^RTa1kyAPIY!%&-fP2- delta 1494 zcmdthSx8h-90u@n$C)ve%*eP-5ZOZz?TV46m6>TSxwM$AZkd^ylgpUPST_G0v(?7B z?ONGh>NM6hTfAD@YL=~9nNg5$3PJQx&^OLd5cD1$_;EhYJ?C6LxVQOXiET4*9cA2- zX)Gf>DI+dzQYN#EU}rTemovuo*DKiIUYR+Yhk5RHQL`z?TxWjS?!~HzKaTPvEJs<{ zj~SMmsS!t@W^WY2~c1{j5qlSGmueOXb1v=O%I5$J26VEYI zvXi&1ykv?)kYNY!B5z0yZyN}@j~z`qBd<5|9LT^1sPTu50GiWBwB43(Kel&;tX)Xm&AkCv7 z3Z@VWrTG*_;S@oUw15`UB3evQw1lE*DJ`Srw1QUBDvF_4ilcZ+pw*N}Ybc4U3W48L4*U+{KWQ>R3*>K5MOOK!Bi9smJLZRchD(8ITECqN6IiRHLvj zOsxJYI7MrPO+_{X-#F_V%~*jor;K4tYt6l58OxJd_Qa28%$~F4)Cu@}tvPKXV|Lc8 zne=--XEI}ltm{MF7|XC`ojYT>mitGr?^79@XMKUc=dTCsrs%s=o8T(+HEW!OV>Q`I zfeX!a7Al`>%EhqS>{g`^>zC;+#uRC*hKai#WoLboZw_1WSsW6rtr^X^{dM=5Vfazx z!pgqJF5yXWo}W;eQye9B->wMcgvwOSaD98f26A8{1C+x)*bf36fP-)d4#N>R3df)VD&aVsfRj)Kr{FZ4 zfwOQ9&cg+`2$!H5YM>U3a2ZTc2Up-Kh)@sLpaHJK4Y&!npb;c!f@Zi4cc2AY;V#^R w`|toB!Xs#d$IuQ>;3;&#Gk6X!;3d3**U$-F@CM$(JLrb@&?C0@fB53@6SWP6qyPW_ diff --git a/advanced/hmi/nspanel_us_code/Program.s.txt b/advanced/hmi/nspanel_us_code/Program.s.txt index c21ddc1..f0e5999 100644 --- a/advanced/hmi/nspanel_us_code/Program.s.txt +++ b/advanced/hmi/nspanel_us_code/Program.s.txt @@ -9,7 +9,7 @@ Program.s int api=0 // 0 = disconnected from HA, 1 = connected to HA int is_entities=0,is_qrcode=0,is_notification=0 int brightness=100,brightness_dim=40,brightness_sleep=0 - int display_mode=2 // 1 = EU, 2 = US, 3 = US landscape + int display_mode=2 // 1 = EU, 2 = US, 3 = US landscape, 4 = blank int charset=1 // 1 = International (original), 2 = CJK //bauds=115200//Configure baudrate recmod=0//Serial data parsing mode:0-Passive mode;1-Active mode diff --git a/advanced/hmi/nspanel_us_code/boot.txt b/advanced/hmi/nspanel_us_code/boot.txt index 8fe509d..c94bf2f 100644 --- a/advanced/hmi/nspanel_us_code/boot.txt +++ b/advanced/hmi/nspanel_us_code/boot.txt @@ -13,53 +13,23 @@ Page boot Events Preinitialize Event sendme - dim=0 + dim=100 vis bt_reboot,0 covx baud,baud_rate.txt,0,0 baud_rate.txt+=" bps" - covx display_mode,aux2.txt,0,0 - covx charset,aux3.txt,0,0 - nspanelevent.txt="{\"page\": \"boot\", \"event\": \"pagechanged\", \"version\": \""+tft_version.txt+"\", \"display_mode\": \""+aux2.txt+"\", \"charset\": \""+aux3.txt+"\"}" - printh 92 - prints "localevent",0 - printh 00 - prints nspanelevent.txt,0 - printh 00 - printh FF FF FF Page Exit Event dim=0 Variable (int32) counter Attributes - ID : 12 + ID : 11 Scope: local Value: 0 Variable (string) aux1 Attributes - ID : 13 - Scope : local - Text : - Max. Text Size: 10 - -Variable (string) nspanelevent - Attributes - ID : 14 - Scope : local - Text : - Max. Text Size: 150 - -Variable (string) aux2 - Attributes - ID : 15 - Scope : local - Text : - Max. Text Size: 10 - -Variable (string) aux3 - Attributes - ID : 21 + ID : 12 Scope : local Text : Max. Text Size: 10 @@ -131,7 +101,7 @@ Text tft_version Dragging : 0 Send Component ID : on press and release Associated Keyboard: none - Text : 4.2dev.2 + Text : 4.2dev.3 Max. Text Size : 9 Text esph_version @@ -156,7 +126,7 @@ Text bluep_version Text baud_rate Attributes - ID : 18 + ID : 14 Scope : local Dragging : 0 Send Component ID : on press and release @@ -166,7 +136,7 @@ Text baud_rate Text framework Attributes - ID : 20 + ID : 15 Scope : local Dragging : 0 Send Component ID : on press and release @@ -184,85 +154,44 @@ Dual-state Button bt_reboot Text : Reboot Max. Text Size : 6 -Timer timer - Attributes - ID : 11 - Scope : local - Period (ms): 65534 - Enabled : yes - - Events - Timer Event - counter.val++ - covx counter.val,aux1.txt,0,0 - covx display_mode,aux2.txt,0,0 - nspanelevent.txt="{\"page\": \"boot\", \"event\": \"timeout\", \"value\": "+aux1.txt+", \"version\": \""+tft_version.txt+"\", \"display_mode\": \""+aux2.txt+"\"}" - bluep_version.txt="Retry: "+aux1.txt - printh 92 - prints "nspanelevent",0 - printh 00 - prints nspanelevent.txt,0 - printh 00 - printh FF FF FF - -Timer wakeup_timer - Attributes - ID : 16 - Scope : local - Period (ms): 50 - Enabled : yes - - Events - Timer Event - if(dim<100) - { - dimdelta=100-dim - dimdelta/=25 - if(dimdelta<1) - { - dimdelta=1 - } - dim+=dimdelta - }else - { - wakeup_timer.en=0 - } - Timer tm_esphome Attributes - ID : 17 + ID : 13 Scope : local Period (ms): 30000 Enabled : yes Events Timer Event - if(baud==115200) + counter.val++ + if(counter.val>60) { - bauds=921600 - }else + rest + } + sendme + printh 91 + prints "display_mode",0 + printh 00 + prints display_mode,0 + printh FF FF FF + printh 91 + prints "charset",0 + printh 00 + prints charset,0 + printh FF FF FF + printh 92 + prints "tft_version",0 + printh 00 + prints tft_version.txt,0 + printh 00 + printh FF FF FF + covx counter.val,aux1.txt,0,0 + esph_version.txt="Retry #"+aux1.txt + sys0=counter.val%10 + if(sys0==0) { - bauds=115200 + baud=115200 } covx baud,baud_rate.txt,0,0 baud_rate.txt+=" bps" -Timer tm_pageid - Attributes - ID : 19 - Scope : local - Period (ms): 2500 - Enabled : yes - - Events - Timer Event - covx display_mode,aux2.txt,0,0 - covx charset,aux3.txt,0,0 - nspanelevent.txt="{\"page\": \"boot\", \"event\": \"pagechanged\", \"version\": \""+tft_version.txt+"\", \"display_mode\": \""+aux2.txt+"\", \"charset\": \""+aux3.txt+"\"}" - printh 92 - prints "localevent",0 - printh 00 - prints nspanelevent.txt,0 - printh 00 - printh FF FF FF - diff --git a/nspanel_blank.tft b/nspanel_blank.tft index 689e1cec939bdb77b5f21633ddf0eedebdf2d793..ec300cc0b9459533440f66a6843a884402443a48 100644 GIT binary patch delta 4189 zcmZ{ndu$X{6vprD-nP4KX`wB$3lz2sG%el2?7C_Th1MX|5FZsaq7rKnNH8cqDgh(3 zsDMvsHROT;A0#G(;3I-i3?e4RSNNlfQ81`Pi5lY{_&^&ptlxKM<}yRNCmd$)@0@$? zJ@d_-?wHgUpX8bcW@7TWDigRlxH?)1b<+||x1vST?p8D{PY1h2H zW9O#lYES-ry7^%9A3HW>%9nn9YU5QquYYmvohDv;l3aLk_%k9*qsW78q;)zmU zqLBiQN+Qi^on0PDE?)M~{mZ*nciy{V@sec9cH_2P%*66Yfy6asmpIef+Y|bqS0z(l z;iM(Imv(oqT=L*U%U0YsOcm=Rp`I*~4A<50 z9D%l+u+V8t9W9a5Rb{yxrAkX>PY)Am_m2^x@0T3%bD{)G4#!JfehP&Kp|L&WE$67aI=;cP%GsxLb@SsG$+^SUiD`GvKBn%V{W~ zPM68)Rzg*k%gsH^+6Xt@AdB6QcU;8T4f#H}=?3}iW;o7n$Y)r-ZjjGz$iwI+IZ{5L zH$i|r$B@Ks5*XFM{Vk+jo+S4I$j`V%C+Fd3z>OilRXO$RgisZuSE= zr-{V^G3@D^j5L63y-AP>X)PCbiGIB8$*~{lC{8%mf*ab zU!B%GyiJIn3lEoX!j*0feF1MB?JQgcKvg`B7rfq3GXQ(k=BvZ8 zmoC7yL{65=RlG_jRonXTuIdE{1T@9#krT)Z)ZkdzU@cHpG3ZLthKSJ>nbeg$pf@;m zC0_$Kx`IKk3bsVg~kO~#=s2kaT#F(m2jsy%V&$}rs+!qk;)A>8N+ zPF=}w&%^Ho_b=ns>CD6L3h3%vxC;}Efm2s59MBbWxILOJYfNaR3oaB|rHZp@#PV&Gp1*e?k0Xe}bCwbtQ zf>Ta%8}Hh4;~^&pY!%!wBsp7sq8f5COgDxwWgvSLZsY`~oaB$>;cLPD%XoEq^6=h( zoW6zEVS+Jm%E^TTa)MJ%a{e7>kJ@~7I5u*E>%N^V@0fl%&O4@C5eR6Czu%ldUZi@H zIHp_GhZCVINgE4gR@6%zB(KmUBNw>E^zf!6327^ARuYP$sJJ3C*hb5sO^&=C$rANjcmxGoaBLH z3Qjr61IHAca*_w+1gD(j0Xe}bCpnMl?#YmogMge!Vp{_$I|XtwOgDxwWgt5QH*$hg zPV#s2@WbFpam-ie{XG0gKu+I;58N910!}%(a6nFQ%1M3@&K|Y->Tqo21lN5#S>7>S zJ;^(!#}UZ-CgjjMCy+-~M;(soarJ1O?3_kI^Op2yGKO&vF1_TaCY!tPN^wy*!XUhX{X5-V9#}X9g_+F2d}B-|9Q?O?@A}eTS`cs&}gVIQp`i zYO9V0IVSSF9?lj0U9;8e4f3(*9T0|PvN~>EuR2olK>P@PKQ-b@&3t$_oEfwxm1vY> zLPx@?xl!)V^dWQ}J{f-}+u=@9F1LW2are|_mE+Af0gr!5^hs4SP415`Lhc~^Z+HwZ zWpCapb!HmMFDg*c=_sE?Xeay=_^-aSt%355Zuxm4Td01=ym%pQ@Wb%!@WXJg{D3-Q zqkLhJI$_JVWi~2``nzIyR(NZQztNd>R?6z{N`@Nof0LS$k@rAx#;xzvu8iC(pY1yP F+W)=Q)Ybq1 delta 3266 zcmZ`*e{56N6~50t2HS}fCnkxL;*dBYO=2f`PTG~QdL-)BF{z-?wd<;-U0s@Wti&`d>YI??0Rk>_oY`?esbxJ`GJnw|5S9uhW>P5jr;o3 ztsn31-a6B}`t6xtoZQoq{OjKaPY>Ri+1p?Jo%tKDefjX(H+CNV?9sPYFXR^X(rB#pEbBGPGGH#3>ONplC_qZ7cGx1AI@v`@*B9&I`4kaVDHV6C-y15Ph`XDB zR=~*XS1RdN!78;FuTQkNs~zUl9yhHcK4?&I>eoLu)RUl$6$U0me=r)&q+T2wjjn7N zjXu9Im40b78izZ!Z7jXTMulJh-BkL;u@`J^+t}ph4HIdbjmC^WAYCjv8r>1~!1Gmj z2m5QpsrE)UPOMLD)VZQ@Q`lbQFO%p$e#sf%YxRl2y_OHtPrgV%Z~Wh6N7rPZ!;KAx?k!yNR{Y2z?| z;G>r3pLHo!E9$3mF!dI7144z!^yW+|JyzBNooRD{|9|~oKQW$}NSDswo_Y#r zklMIapIY2LK>L9H$NBV|{R6#;L~laQM=EaRLvCu}E8M5dY9m zVZaCcw69UaU@{F#W&dGWN?ED`T@7-wf*Le^0}Z{1ewv@#bU@SNpmoygJX=8_jsW%Xem~XlnE*`zNLFE42^oQvsxmCd zLa8v17wLCEm#P9?rRf>adI2}jR$;3jR?&0%J`dKKHzE&OIRUFPX7H}Q0kmUuc5<>FmIta>y^VVgN0q*3iP3cY}3`z4vn zGWt=kVBI`iLyvgka0hs{22F|cObzwnaiNC3t%*1%YEhZQ^zzqg=@G-^J+;`W34}OT zM=?GZL=XR7OH+*k%EeG>9xfU=Qj>Rt&|9I;!%EEqEk|ndG0-~YRlXTQAtql7(RMvS znt&L4V1fa_Ie8z(d~EKFR)Qp2zbbGS8%F`4srr+L^6ncq*Hoe zEnJvN@PresIERKv7wTxcV z*X*~u%b*TPDOMVoBkB-rKM%KHSK|KEaS!sQBFc{YI?n>t75==G3l7qWIsoc9K(xmIu)vMD`$ zd^SL9djuCN-ReI3Ru}9em=kZg5gD%U5*rw!P5Ar)=BkT*+rGc9d7KRKl&KBr0L{K-H`ikHoAd+9X%E_ zuF=Jh>emrI-bn+wOeOcmXfpXBz(M?FdKNYflXw}X)KMs?8u!w9)vT_nxRQ9w`j&bL zpA(X*f&Uq!{f&n(*N>k86ZSaF&SF_3JR7G5*C98*7pHfVX;6QGxo~y1!-`>nQN_hPwPTs^yBM_?boip%Bi zS?;c~mwVqZ4T(pLsx~Yu#3>WI+uujo$be7XUz}TfepbkL(eCc2`n8ki61uzYxX~jC ZB7D}Q??F8JV2kN&-4=D)Co=)V8}