From 2c9c6aea301c989dd0ce523a5c3fd62ad3991279 Mon Sep 17 00:00:00 2001 From: oarcher Date: Thu, 15 Aug 2024 12:18:22 +0200 Subject: [PATCH] handle bad connection and reboot_timeout --- esphome/components/modem/__init__.py | 7 + esphome/components/modem/modem_component.cpp | 131 +++++++++++------- esphome/components/modem/modem_component.h | 21 +-- .../components/modem/sensor/modem_sensor.cpp | 23 +-- 4 files changed, 107 insertions(+), 75 deletions(-) diff --git a/esphome/components/modem/__init__.py b/esphome/components/modem/__init__.py index bb72d6a0bc..16c227ff6e 100644 --- a/esphome/components/modem/__init__.py +++ b/esphome/components/modem/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_ON_CONNECT, CONF_ON_DISCONNECT, CONF_PASSWORD, + CONF_REBOOT_TIMEOUT, CONF_RX_PIN, CONF_TRIGGER_ID, CONF_TX_PIN, @@ -88,6 +89,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, cv.Optional(CONF_ENABLE_CMUX, default=False): cv.boolean, cv.Optional(CONF_DEBUG, default=False): cv.boolean, + cv.Optional( + CONF_REBOOT_TIMEOUT, default="10min" + ): cv.positive_time_period_milliseconds, cv.Optional(CONF_ON_NOT_RESPONDING): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -168,6 +172,9 @@ async def to_code(config): cg.add_define("USE_MODEM") var = cg.new_Pvariable(config[CONF_ID]) + + cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) + if use_address := config.get(CONF_USE_ADDRESS, None): cg.add(var.set_use_address(use_address)) diff --git a/esphome/components/modem/modem_component.cpp b/esphome/components/modem/modem_component.cpp index 3a85e74581..2711927009 100644 --- a/esphome/components/modem/modem_component.cpp +++ b/esphome/components/modem/modem_component.cpp @@ -54,6 +54,21 @@ void ModemComponent::enable_debug() { // esp_log_level_set("CMUX Received", ESP_LOG_VERBOSE); } +bool ModemComponent::is_modem_connected(bool verbose) { + float rssi, ber; + int network_mode = 0; + bool network_attached = this->is_network_attached_(); + this->get_signal_quality(rssi, ber); + this->dce->get_network_system_mode(network_mode); + + bool connected = (network_mode != 0) && (!std::isnan(rssi)) && network_attached; + + ESP_LOGD(TAG, "Modem internal network status: %s (attached: %s, type: %s, rssi: %.0fdB %s, ber: %.0f%%)", + connected ? "Good" : "BAD", network_attached ? "Yes" : "NO", + network_system_mode_to_string(network_mode).c_str(), rssi, get_signal_bars(rssi).c_str(), ber); + return connected; +} + AtCommandResult ModemComponent::send_at(const std::string &cmd, uint32_t timeout) { AtCommandResult at_command_result; at_command_result.success = false; @@ -141,13 +156,13 @@ void ModemComponent::enable() { if (this->component_state_ == ModemComponentState::DISABLED) { this->component_state_ = ModemComponentState::DISCONNECTED; } - this->internal_state_.start = true; this->internal_state_.enabled = true; } void ModemComponent::disable() { ESP_LOGD(TAG, "Disabling modem"); this->internal_state_.enabled = false; + this->internal_state_.starting = false; if (this->component_state_ != ModemComponentState::CONNECTED) { this->component_state_ = ModemComponentState::DISCONNECTED; } @@ -156,14 +171,32 @@ void ModemComponent::disable() { void ModemComponent::reconnect() { if (!this->internal_state_.reconnect) { this->internal_state_.reconnect = true; - this->component_state_ = ModemComponentState::NOT_RESPONDING; + this->internal_state_.connected = false; + this->component_state_ = ModemComponentState::DISCONNECTED; // if reconnect fail, let some time before retry - set_timeout(120000, [this]() { this->internal_state_.reconnect = false; }); + set_timeout("retry_reconnect", this->reconnect_grace_period_, + [this]() { this->internal_state_.reconnect = false; }); } else { ESP_LOGD(TAG, "Reconnecting already in progress."); } } +bool ModemComponent::get_signal_quality(float &rssi, float &ber) { + rssi = NAN; + ber = NAN; + int modem_rssi = 99; + int modem_ber = 99; + if (this->modem_ready() && + (global_modem_component->dce->get_signal_quality(modem_rssi, modem_ber) == command_result::OK)) { + if (modem_rssi != 99) + rssi = -113 + (modem_rssi * 2); + if (modem_ber != 99) + ber = 0.1f * (modem_ber * modem_ber); + return true; + } + return false; +} + network::IPAddresses ModemComponent::get_ip_addresses() { network::IPAddresses addresses; esp_netif_ip_info_t ip; @@ -243,7 +276,6 @@ void ModemComponent::loop() { static uint32_t next_loop_millis = millis(); static uint32_t last_health_check = millis(); static bool connecting = false; - static uint8_t network_attach_retry = 10; if ((millis() < next_loop_millis)) { // some commands need some delay @@ -265,13 +297,13 @@ void ModemComponent::loop() { ESP_LOGD(TAG, "Will check that the modem is on in %.1fs...", float(this->power_tonuart_) / 1000); break; case ModemPowerState::TONUART: - this->internal_state_.power_transition = false; ESP_LOGD(TAG, "TONUART check sync"); if (!this->modem_sync_()) { ESP_LOGE(TAG, "Unable to power on the modem"); } else { ESP_LOGI(TAG, "Modem powered ON"); } + this->internal_state_.power_transition = false; break; case ModemPowerState::TOFF: delay(10); @@ -301,23 +333,14 @@ void ModemComponent::loop() { switch (this->component_state_) { case ModemComponentState::NOT_RESPONDING: - if (this->internal_state_.start) { - if (this->modem_ready(true) && !this->internal_state_.connected) { - ESP_LOGI(TAG, "Modem recovered"); - this->status_clear_warning(); - this->component_state_ = ModemComponentState::DISCONNECTED; + if (this->internal_state_.starting) { + ESP_LOGW(TAG, "Modem not responding, resetting..."); + this->internal_state_.connected = false; + // this->modem_lazy_init_(); + if (!this->modem_sync_()) { + ESP_LOGE(TAG, "Unable to recover modem"); } else { - ESP_LOGI(TAG, "Resetting modem"); - this->internal_state_.connected = false; - this->modem_lazy_init_(); - if (!this->modem_sync_()) { - ESP_LOGE(TAG, "Unable to recover modem"); - } else { - this->component_state_ = ModemComponentState::DISCONNECTED; - } - // if (!this->internal_state_.powered_on) { - // this->poweron_(); - // } + this->component_state_ = ModemComponentState::DISCONNECTED; } } break; @@ -330,31 +353,30 @@ void ModemComponent::loop() { break; } else if (!this->internal_state_.modem_synced) { if (!this->modem_sync_()) { - ESP_LOGE(TAG, "Modem not responding"); this->component_state_ = ModemComponentState::NOT_RESPONDING; } } - if (this->internal_state_.start) { + if (this->internal_state_.starting) { + float time_left_s = float(this->timeout_ - (millis() - this->internal_state_.startms)) / 1000; // want to connect + if ((millis() - this->internal_state_.startms) > this->timeout_) { + this->abort_("Timeout while trying to connect"); + } if (!connecting) { // wait for the modem be attached to a network, start ppp, and set connecting=true - if (is_network_attached_()) { - network_attach_retry = 10; + if (this->is_modem_connected()) { if (this->start_ppp_()) { connecting = true; - next_loop_millis = millis() + 2000; // delay for next loop + } else { + ESP_LOGE(TAG, "modem is unable to enter PPP (time left before abort: %.0fs)", time_left_s); + this->stop_ppp_(); + this->is_modem_connected(); } } else { - ESP_LOGD(TAG, "Waiting for the modem to be attached to a network (left retries: %" PRIu8 ")", - network_attach_retry); - network_attach_retry--; - if (network_attach_retry == 0) { - ESP_LOGE(TAG, "modem is unable to attach to a network"); - network_attach_retry = 10; - this->component_state_ = ModemComponentState::NOT_RESPONDING; - } - next_loop_millis = millis() + 1000; // delay to retry + ESP_LOGW(TAG, "Waiting for the modem to be attached to a network (time left before abort: %.0fs)", + time_left_s); + next_loop_millis = millis() + 5000; // delay to retry } } else { // connecting @@ -378,7 +400,8 @@ void ModemComponent::loop() { } } } else { - this->internal_state_.start = true; + this->internal_state_.starting = true; + this->internal_state_.startms = millis(); } } else { this->component_state_ = ModemComponentState::DISABLED; @@ -386,21 +409,19 @@ void ModemComponent::loop() { break; case ModemComponentState::CONNECTED: + this->internal_state_.starting = false; if (this->internal_state_.enabled) { if (!this->internal_state_.connected) { this->status_set_warning("Connection via Modem lost!"); this->component_state_ = ModemComponentState::DISCONNECTED; break; } - // clear flags if previously set by this->reconnect() - this->internal_state_.reconnect = false; if (this->cmux_ && (millis() - last_health_check) > 30000) { - ESP_LOGD(TAG, "modem health check"); last_health_check = millis(); - if (!this->get_imei()) { - ESP_LOGW(TAG, "modem health check failed"); - this->component_state_ = ModemComponentState::NOT_RESPONDING; + if (!this->is_modem_connected()) { + ESP_LOGW(TAG, "Reconnecting..."); + this->reconnect(); } } } else { @@ -626,7 +647,9 @@ bool ModemComponent::is_network_attached_() { bool ModemComponent::start_ppp_() { this->internal_state_.connect_begin = millis(); this->status_set_warning("Starting connection"); - watchdog::WatchdogManager wdt(10000); + watchdog::WatchdogManager wdt(15000); // mini 10000 + + uint32_t now = millis(); // will be set to true on event IP_EVENT_PPP_GOT_IP this->internal_state_.got_ipv4_address = false; @@ -643,7 +666,9 @@ bool ModemComponent::start_ppp_() { } if (!status) { - ESP_LOGE(TAG, "Unable to change modem mode to PPP"); + ESP_LOGE(TAG, "Unable to change modem mode to PPP after %" PRIu32 "ms", millis() - now); + } else { + ESP_LOGD(TAG, "Entered PPP after %" PRIu32 "ms", millis() - now); } return status; @@ -658,7 +683,7 @@ bool ModemComponent::stop_ppp_() { status = this->dce->set_mode(modem_mode::COMMAND_MODE); } if (!status) { - ESP_LOGE(TAG, "Error exiting PPP"); + ESP_LOGW(TAG, "Error exiting PPP"); } return status; } @@ -709,6 +734,12 @@ void ModemComponent::poweroff_() { } } +void ModemComponent::abort_(const std::string &message) { + ESP_LOGE(TAG, "Aborting: %s", message.c_str()); + this->send_at("AT+CFUN=1,1"); + App.reboot(); +} + void ModemComponent::dump_connect_params_() { if (!this->internal_state_.connected) { ESP_LOGCONFIG(TAG, "Modem connection: Not connected"); @@ -716,11 +747,11 @@ void ModemComponent::dump_connect_params_() { } esp_netif_ip_info_t ip; esp_netif_get_ip_info(this->ppp_netif_, &ip); - ESP_LOGCONFIG(TAG, "Modem connection:"); - ESP_LOGCONFIG(TAG, " IP Address : %s", network::IPAddress(&ip.ip).str().c_str()); - ESP_LOGCONFIG(TAG, " Hostname : '%s'", App.get_name().c_str()); - ESP_LOGCONFIG(TAG, " Subnet : %s", network::IPAddress(&ip.netmask).str().c_str()); - ESP_LOGCONFIG(TAG, " Gateway : %s", network::IPAddress(&ip.gw).str().c_str()); + ESP_LOGI(TAG, "Modem connection:"); + ESP_LOGI(TAG, " IP Address : %s", network::IPAddress(&ip.ip).str().c_str()); + ESP_LOGI(TAG, " Hostname : '%s'", App.get_name().c_str()); + ESP_LOGI(TAG, " Subnet : %s", network::IPAddress(&ip.netmask).str().c_str()); + ESP_LOGI(TAG, " Gateway : %s", network::IPAddress(&ip.gw).str().c_str()); const ip_addr_t *dns_main_ip = dns_getserver(ESP_NETIF_DNS_MAIN); const ip_addr_t *dns_backup_ip = dns_getserver(ESP_NETIF_DNS_BACKUP); diff --git a/esphome/components/modem/modem_component.h b/esphome/components/modem/modem_component.h index f2a67dec4d..36469fc4e3 100644 --- a/esphome/components/modem/modem_component.h +++ b/esphome/components/modem/modem_component.h @@ -52,6 +52,7 @@ struct AtCommandResult { class ModemComponent : public Component { public: + void set_reboot_timeout(uint16_t timeout) { this->timeout_ = timeout; } void set_use_address(const std::string &use_address) { this->use_address_ = use_address; } void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } @@ -67,8 +68,10 @@ class ModemComponent : public Component { void enable_cmux() { this->cmux_ = true; } void enable_debug(); void add_init_at_command(const std::string &cmd) { this->init_at_commands_.push_back(cmd); } - bool is_connected() { return this->component_state_ == ModemComponentState::CONNECTED; } + bool is_connected() { return this->internal_state_.connected; } bool is_disabled() { return this->component_state_ == ModemComponentState::DISABLED; } + bool is_modem_connected(bool verbose); // this if for modem only, not PPP + bool is_modem_connected() { return this->is_modem_connected(true); } AtCommandResult send_at(const std::string &cmd, uint32_t timeout); AtCommandResult send_at(const std::string &cmd); AtCommandResult get_imei(); @@ -78,6 +81,7 @@ class ModemComponent : public Component { void enable(); void disable(); void reconnect(); + bool get_signal_quality(float &rssi, float &ber); network::IPAddresses get_ip_addresses(); std::string get_use_address() const; @@ -90,12 +94,7 @@ class ModemComponent : public Component { void loop() override; void dump_config() override { this->dump_connect_params_(); } float get_setup_priority() const override { return setup_priority::WIFI + 1; } // just before WIFI - bool can_proceed() override { - if (!this->internal_state_.enabled) { - return true; - } - return this->is_connected(); - }; + bool can_proceed() override { return network::is_disabled() || this->is_connected(); }; void add_on_state_callback(std::function &&callback) { this->on_state_callback_.add(std::move(callback)); } @@ -113,10 +112,12 @@ class ModemComponent : public Component { bool stop_ppp_(); void poweron_(); void poweroff_(); + void abort_(const std::string &message); static void ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); void dump_connect_params_(); // Attributes from yaml config + uint16_t timeout_; InternalGPIOPin *tx_pin_; InternalGPIOPin *rx_pin_; std::string model_; @@ -142,7 +143,7 @@ class ModemComponent : public Component { size_t uart_event_task_stack_size_ = 4096; // 2000-6000 uint8_t uart_event_task_priority_ = 5; // 3-22 uint32_t command_delay_ = 1000; // timeout for AT commands - uint32_t update_interval_ = 60 * 1000; + uint32_t reconnect_grace_period_ = 30000; // let some time to mqtt or api to reconnect before retry // Changes will trigger user callback ModemComponentState component_state_{ModemComponentState::DISABLED}; @@ -153,7 +154,9 @@ class ModemComponent : public Component { esp_netif_t *ppp_netif_{nullptr}; struct InternalState { - bool start{false}; + bool starting{false}; + // trying to connect timestamp (for timeout) + uint32_t startms; bool enabled{false}; bool connected{false}; bool got_ipv4_address{false}; diff --git a/esphome/components/modem/sensor/modem_sensor.cpp b/esphome/components/modem/sensor/modem_sensor.cpp index 57eb5d5dec..b5c4a3c6b6 100644 --- a/esphome/components/modem/sensor/modem_sensor.cpp +++ b/esphome/components/modem/sensor/modem_sensor.cpp @@ -47,23 +47,14 @@ void ModemSensor::update() { } void ModemSensor::update_signal_sensors_() { - float rssi = NAN; - float ber = NAN; if (this->rssi_sensor_ || this->ber_sensor_) { - int modem_rssi = 99; - int modem_ber = 99; - if (global_modem_component->modem_ready() && - (global_modem_component->dce->get_signal_quality(modem_rssi, modem_ber) == command_result::OK)) { - if (modem_rssi != 99) - rssi = -113 + (modem_rssi * 2); - if (modem_ber != 99) - ber = 0.1f * (modem_ber * modem_ber); - - if (this->rssi_sensor_) - this->rssi_sensor_->publish_state(rssi); - if (this->ber_sensor_) - this->ber_sensor_->publish_state(ber); - } + float rssi; + float ber; + global_modem_component->get_signal_quality(rssi, ber); + if (this->rssi_sensor_) + this->rssi_sensor_->publish_state(rssi); + if (this->ber_sensor_) + this->ber_sensor_->publish_state(ber); } }