handle bad connection and reboot_timeout

This commit is contained in:
oarcher 2024-08-15 12:18:22 +02:00
parent 3102f6bfd5
commit 2c9c6aea30
4 changed files with 107 additions and 75 deletions

View file

@ -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))

View file

@ -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,24 +333,15 @@ 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;
} else {
ESP_LOGI(TAG, "Resetting modem");
if (this->internal_state_.starting) {
ESP_LOGW(TAG, "Modem not responding, resetting...");
this->internal_state_.connected = false;
this->modem_lazy_init_();
// 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_();
// }
}
}
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);

View file

@ -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<void(ModemComponentState, ModemComponentState)> &&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};

View file

@ -47,24 +47,15 @@ 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);
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);
}
}
}
std::map<std::string, std::string> get_gnssinfo_tokens(const std::string &gnss_info) {