This commit is contained in:
oarcher 2024-08-01 23:00:16 +02:00
parent 00b9e83d0e
commit 62fb07e595
2 changed files with 464 additions and 418 deletions

View file

@ -51,15 +51,82 @@ ModemComponent::ModemComponent() {
global_modem_component = this; global_modem_component = this;
} }
void ModemComponent::dump_config() { this->dump_connect_params_(); } std::string ModemComponent::send_at(const std::string &cmd) {
std::string result;
command_result status;
ESP_LOGV(TAG, "Sending command: %s", cmd.c_str());
status = this->dce->at(cmd, result, this->command_delay_ * 5);
ESP_LOGV(TAG, "Result for command %s: %s (status %s)", cmd.c_str(), result.c_str(),
command_result_to_string(status).c_str());
if (status != esp_modem::command_result::OK) {
result = "ERROR";
}
return result;
}
float ModemComponent::get_setup_priority() const { return setup_priority::WIFI + 1; } // just before WIFI bool ModemComponent::get_imei(std::string &result) {
// wrapper around this->dce->get_imei() that check that the result is valid
// (so it can be used to check if the modem is responding correctly (a simple 'AT' cmd is sometime not enough))
command_result status;
status = this->dce->get_imei(result);
bool success = true;
bool ModemComponent::can_proceed() { if (status == command_result::OK && result.length() == 15) {
if (!this->enabled_) { for (char c : result) {
if (!isdigit(static_cast<unsigned char>(c))) {
success = false;
break;
}
}
} else {
success = false;
}
if (!success) {
result = "UNAVAILABLE";
}
return success;
}
bool ModemComponent::get_power_status() {
#ifdef USE_MODEM_STATUS
// This code is not fully checked. The status pin seems to be flickering on Lilygo T-SIM7600
return this->status_pin_->digital_read();
#else
if (!this->cmux_ && this->internal_state_.connected) {
// Data mode, connected: assume power is OK
return true; return true;
} }
return this->is_connected(); return this->modem_ready();
#endif
}
bool ModemComponent::modem_ready() {
// check if the modem is ready to answer AT commands
std::string imei;
if (this->dce && this->get_imei(imei)) {
// we are sure that the modem is on
this->internal_state_.powered_on = true;
return true;
}
return false;
}
void ModemComponent::enable() {
ESP_LOGD(TAG, "Enabling modem");
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;
if (this->component_state_ != ModemComponentState::CONNECTED) {
this->component_state_ = ModemComponentState::DISCONNECTED;
}
} }
network::IPAddresses ModemComponent::get_ip_addresses() { network::IPAddresses ModemComponent::get_ip_addresses() {
@ -78,9 +145,80 @@ std::string ModemComponent::get_use_address() const {
return this->use_address_; return this->use_address_;
} }
void ModemComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } void ModemComponent::dump_dce_status() {
bool watchdog = false;
if (!this->watchdog_) {
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
watchdog = true;
}
bool ModemComponent::is_connected() { return this->state_ == ModemComponentState::CONNECTED; } if (this->internal_state_.connected && !this->cmux_) {
if (!this->dce->set_mode(modem_mode::COMMAND_MODE)) {
ESP_LOGW(TAG, "Unable to enter temporary command mode");
return;
}
}
if (this->internal_state_.modem_synced && this->modem_ready()) {
ESP_LOGCONFIG(TAG, "Modem status:");
command_result err;
std::string result;
// err = this->dce->get_module_name(result);
// if (err != command_result::OK) {
// result = "command " + command_result_to_string(err);
// }
// ESP_LOGCONFIG(TAG, " Module name : %s", result.c_str());
int rssi;
int ber;
err = this->dce->get_signal_quality(rssi, ber);
if (err != command_result::OK) {
result = "command " + command_result_to_string(err);
} else {
float ber_f;
if (ber != 99) {
ber_f = float(ber);
} else
ber_f = {};
std::ostringstream oss;
oss << "rssi " << rssi << "(" << (rssi * 100) / 31 << "%), ber " << ber << "(" << (ber_f * 100) / 7 << "%)";
result = oss.str();
}
ESP_LOGCONFIG(TAG, " Signal quality : %s", result.c_str());
// int network_attachment_state;
// err = this->dce->get_network_attachment_state(network_attachment_state);
// if (err == command_result::OK) {
// result = network_attachment_state ? "Yes" : "No";
// } else {
// result = "command " + command_result_to_string(err);
// }
// ESP_LOGCONFIG(TAG, " Attached to network: %s", result.c_str());
//
// err = this->dce->get_operator_name(result);
// if (err != command_result::OK) {
// result = "command " + command_result_to_string(err);
// }
// ESP_LOGCONFIG(TAG, " Operator : %s", result.c_str());
//
// int act;
// err = this->dce->get_network_system_mode(act);
// if (err != command_result::OK) {
// result = "command " + command_result_to_string(err);
// } else {
// std::ostringstream oss;
// oss << act;
// result = oss.str();
// }
// ESP_LOGCONFIG(TAG, " Access technology : %s", result.c_str());
}
if (this->internal_state_.connected && !this->cmux_) {
if (this->dce->resume_data_mode() != command_result::OK) {
ESP_LOGW(TAG, "Unable to resume data mode. Reconnecting...");
this->internal_state_.connected = false;
}
}
if (watchdog)
this->watchdog_.reset();
}
void ModemComponent::setup() { void ModemComponent::setup() {
ESP_LOGI(TAG, "Setting up Modem..."); ESP_LOGI(TAG, "Setting up Modem...");
@ -88,14 +226,14 @@ void ModemComponent::setup() {
if (this->power_pin_) { if (this->power_pin_) {
this->power_pin_->setup(); this->power_pin_->setup();
// as we have a power pin, we assume that the power is off // as we have a power pin, we assume that the power is off
this->powered_on_ = false; this->internal_state_.powered_on = false;
if (this->enabled_) { if (this->internal_state_.enabled) {
this->poweron_(); this->poweron_();
} }
} else { } else {
// no status pin, we assume that the power is on // no status pin, we assume that the power is on
this->powered_on_ = true; this->internal_state_.powered_on = true;
} }
if (this->status_pin_) { if (this->status_pin_) {
@ -116,7 +254,7 @@ void ModemComponent::setup() {
} else { } else {
ESP_LOGCONFIG(TAG, " Status pin: Not defined"); ESP_LOGCONFIG(TAG, " Status pin: Not defined");
} }
ESP_LOGCONFIG(TAG, " Enabled : %s", this->enabled_ ? "Yes" : "No"); ESP_LOGCONFIG(TAG, " Enabled : %s", this->internal_state_.enabled ? "Yes" : "No");
ESP_LOGCONFIG(TAG, " Use CMUX : %s", this->cmux_ ? "Yes" : "No"); ESP_LOGCONFIG(TAG, " Use CMUX : %s", this->cmux_ ? "Yes" : "No");
ESP_LOGV(TAG, "PPP netif setup"); ESP_LOGV(TAG, "PPP netif setup");
@ -147,6 +285,232 @@ void ModemComponent::setup() {
ESP_LOGV(TAG, "Setup finished"); ESP_LOGV(TAG, "Setup finished");
} }
void ModemComponent::loop() {
static ModemComponentState last_state = this->component_state_;
static uint32_t next_loop_millis = millis();
static bool connecting = false;
static bool disconnecting = false;
static uint8_t network_attach_retry = 10;
static uint8_t ip_lost_retries = 10;
if ((millis() < next_loop_millis)) {
// some commands need some delay
yield();
return;
}
#ifdef USE_MODEM_POWER
if (this->internal_state_.power_transition) {
if (!this->watchdog_)
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
// A power state is used to handle long tonuart/toffuart delay
switch (this->internal_state_.power_state) {
case ModemPowerState::TON:
this->power_pin_->digital_write(false);
delay(USE_MODEM_POWER_TON);
this->power_pin_->digital_write(true);
next_loop_millis = millis() + USE_MODEM_POWER_TONUART; // delay for next loop
this->internal_state_.power_state = ModemPowerState::TONUART;
ESP_LOGD(TAG, "Will check that the modem is on in %.1fs...", float(USE_MODEM_POWER_TONUART) / 1000);
break;
case ModemPowerState::TONUART:
this->internal_state_.power_transition = false;
if (!this->modem_sync_()) {
ESP_LOGE(TAG, "Unable to power on the modem");
this->internal_state_.powered_on = false;
} else {
ESP_LOGI(TAG, "Modem powered ON");
this->internal_state_.powered_on = true;
this->internal_state_.modem_synced = false;
this->watchdog_.reset();
}
break;
case ModemPowerState::TOFF:
delay(10);
this->power_pin_->digital_write(false);
delay(USE_MODEM_POWER_TOFF);
this->power_pin_->digital_write(true);
this->internal_state_.power_state = ModemPowerState::TOFFUART;
ESP_LOGD(TAG, "Will check that the modem is off in %.1fs...", float(USE_MODEM_POWER_TOFFUART) / 1000);
next_loop_millis = millis() + USE_MODEM_POWER_TOFFUART; // delay for next loop
break;
case ModemPowerState::TOFFUART:
this->internal_state_.power_transition = false;
if (this->modem_ready()) {
ESP_LOGE(TAG, "Unable to power off the modem");
this->internal_state_.powered_on = true;
} else {
ESP_LOGI(TAG, "Modem powered OFF");
this->internal_state_.powered_on = false;
this->internal_state_.modem_synced = false;
this->watchdog_.reset();
}
break;
}
yield();
return;
}
#endif // USE_MODEM_POWER
switch (this->component_state_) {
case ModemComponentState::NOT_RESPONDING:
if (this->internal_state_.start) {
if (this->modem_ready()) {
ESP_LOGI(TAG, "Modem recovered");
this->status_clear_warning();
this->component_state_ = ModemComponentState::DISCONNECTED;
} else {
if (!this->internal_state_.powered_on) {
this->poweron_();
} else if (this->not_responding_cb_) {
if (!this->not_responding_cb_->is_action_running()) {
ESP_LOGD(TAG, "Calling 'on_not_responding' callback");
this->not_responding_cb_->trigger();
}
} else {
ESP_LOGW(TAG, "Modem not responding, and no 'on_not_responding' action defined");
}
}
}
break;
case ModemComponentState::DISCONNECTED:
if (this->internal_state_.enabled) {
// be sure the modem is on and synced
if (!this->internal_state_.powered_on) {
this->poweron_();
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) {
// want to connect
if (!connecting) {
// wait for the modem be attached to a network, start ppp, and set connecting=true
if (!this->watchdog_)
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
if (is_network_attached_()) {
network_attach_retry = 10;
if (this->start_ppp_()) {
connecting = true;
next_loop_millis = millis() + 2000; // delay for next loop
}
} 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");
if (this->power_pin_) {
this->poweroff_();
} else {
this->component_state_ = ModemComponentState::NOT_RESPONDING;
}
}
next_loop_millis = millis() + 1000; // delay to retry
}
} else {
// connecting
if (!this->internal_state_.connected) {
// wait until this->internal_state_.connected set to true by IP_EVENT_PPP_GOT_IP
next_loop_millis = millis() + 1000; // delay for next loop
// connecting timeout
if (millis() - this->internal_state_.connect_begin > 15000) {
ESP_LOGW(TAG, "Connecting via Modem failed! Re-connecting...");
// TODO: exit data/cmux without error check
connecting = false;
}
} else {
connecting = false;
ESP_LOGI(TAG, "Connected via Modem");
this->component_state_ = ModemComponentState::CONNECTED;
this->dump_connect_params_();
this->status_clear_warning();
this->watchdog_.reset();
}
}
} else {
this->internal_state_.start = true;
}
} else {
this->component_state_ = ModemComponentState::DISABLED;
this->watchdog_.reset();
}
break;
case ModemComponentState::CONNECTED:
if (this->internal_state_.enabled) {
if (!this->internal_state_.connected) {
this->status_set_warning("Connection via Modem lost!");
this->component_state_ = ModemComponentState::DISCONNECTED;
}
disconnecting = false;
} else {
if (this->internal_state_.connected) {
// connected but disbled, so disconnect
if (!disconnecting) {
disconnecting = true;
ip_lost_retries = 10;
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
ESP_LOGD(TAG, "Disconnecting...");
this->stop_ppp_();
delay(200); // NOLINT
ESP_LOGD(TAG, "Disconnected after %.1fmin", float(this->internal_state_.connect_begin) / (1000 * 60));
} else {
// disconnecting
// Waiting for IP_EVENT_PPP_LOST_IP.
// This can take a long time, so we ckeck the IP addr, and trigger the event manualy if it's null.
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(this->ppp_netif_, &ip_info);
if (ip_info.ip.addr == 0) {
// lost IP
esp_event_post(IP_EVENT, IP_EVENT_PPP_LOST_IP, nullptr, 0, 0);
} else {
ESP_LOGD(TAG, "Waiting for lost IP... (retries %" PRIu8 ")", ip_lost_retries);
ip_lost_retries--;
if (ip_lost_retries == 0) {
// Something goes wrong, we have still an IP
ESP_LOGE(TAG, "No IP lost event recieved. Sending one manually");
esp_event_post(IP_EVENT, IP_EVENT_PPP_LOST_IP, nullptr, 0, 0);
}
}
next_loop_millis = millis() + 2000; // delay for next loop
}
} else { // if (this->internal_state_.connected)
// ip lost as expected
ESP_LOGI(TAG, "PPPoS disconnected");
this->component_state_ = ModemComponentState::DISCONNECTED;
}
}
break;
case ModemComponentState::DISABLED:
if (this->internal_state_.enabled) {
this->component_state_ = ModemComponentState::DISCONNECTED;
ESP_LOGE(TAG, "here");
} else if (this->internal_state_.powered_on) {
this->poweroff_();
}
next_loop_millis = millis() + 2000; // delay for next loop
break;
}
if (this->component_state_ != last_state) {
ESP_LOGV(TAG, "State changed: %s -> %s", state_to_string(last_state).c_str(),
state_to_string(this->component_state_).c_str());
this->on_state_callback_.call(last_state, this->component_state_);
last_state = this->component_state_;
}
}
void ModemComponent::modem_lazy_init_() { void ModemComponent::modem_lazy_init_() {
// destroy previous dte/dce, and recreate them. // destroy previous dte/dce, and recreate them.
// no communication is done with the modem. // no communication is done with the modem.
@ -239,21 +603,22 @@ bool ModemComponent::modem_sync_() {
if (!status) { if (!status) {
ESP_LOGW(TAG, "modem not responding after %" PRIu32 "ms.", elapsed_ms); ESP_LOGW(TAG, "modem not responding after %" PRIu32 "ms.", elapsed_ms);
this->powered_on_ = false; this->internal_state_.powered_on = false;
} }
} }
if (status) { if (status) {
elapsed_ms = millis() - start_ms; elapsed_ms = millis() - start_ms;
ESP_LOGD(TAG, "Connected to the modem in %" PRIu32 "ms", elapsed_ms); ESP_LOGD(TAG, "Connected to the modem in %" PRIu32 "ms", elapsed_ms);
this->send_init_at_();
if (!this->prepare_sim_()) { if (!this->prepare_sim_()) {
// fatal error // fatal error
this->disable(); this->disable();
status = false; status = false;
} }
this->send_init_at_();
} }
this->modem_synced_ = status; this->internal_state_.modem_synced = status;
return status; return status;
} }
@ -296,19 +661,27 @@ void ModemComponent::send_init_at_() {
} else { } else {
ESP_LOGI(TAG, "'init_at' '%s' result: %s", cmd.c_str(), result.c_str()); ESP_LOGI(TAG, "'init_at' '%s' result: %s", cmd.c_str(), result.c_str());
} }
delay(this->command_delay_);
yield(); yield();
} }
} }
bool ModemComponent::is_network_attached_() {
int network_attachment_state;
command_result err = this->dce->get_network_attachment_state(network_attachment_state);
if (err == command_result::OK) {
return network_attachment_state;
} else
return false;
}
bool ModemComponent::start_ppp_() { bool ModemComponent::start_ppp_() {
this->connect_begin_ = millis(); this->internal_state_.connect_begin = millis();
this->status_set_warning("Starting connection"); this->status_set_warning("Starting connection");
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000); this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
// will be set to true on event IP_EVENT_PPP_GOT_IP // will be set to true on event IP_EVENT_PPP_GOT_IP
this->got_ipv4_address_ = false; this->internal_state_.got_ipv4_address = false;
this->dump_dce_status_();
ESP_LOGD(TAG, "Asking the modem to enter PPP"); ESP_LOGD(TAG, "Asking the modem to enter PPP");
@ -351,282 +724,25 @@ void ModemComponent::ip_event_handler(void *arg, esp_event_base_t event_base, in
event = (ip_event_got_ip_t *) event_data; event = (ip_event_got_ip_t *) event_data;
ip_info = &event->ip_info; ip_info = &event->ip_info;
ESP_LOGD(TAG, "[IP event] Got IP " IPSTR, IP2STR(&ip_info->ip)); ESP_LOGD(TAG, "[IP event] Got IP " IPSTR, IP2STR(&ip_info->ip));
global_modem_component->got_ipv4_address_ = true; global_modem_component->internal_state_.got_ipv4_address = true;
global_modem_component->connected_ = true; global_modem_component->internal_state_.connected = true;
break; break;
case IP_EVENT_PPP_LOST_IP: case IP_EVENT_PPP_LOST_IP:
if (global_modem_component->connected_) { if (global_modem_component->internal_state_.connected) {
// do not log message if we are not connected // do not log message if we are not connected
ESP_LOGD(TAG, "[IP event] Lost IP"); ESP_LOGD(TAG, "[IP event] Lost IP");
} }
global_modem_component->got_ipv4_address_ = false; global_modem_component->internal_state_.got_ipv4_address = false;
global_modem_component->connected_ = false; global_modem_component->internal_state_.connected = false;
break; break;
} }
} }
void ModemComponent::loop() {
static ModemComponentState last_state = this->state_;
static uint32_t next_loop_millis = millis();
static bool connecting = false;
static bool disconnecting = false;
static uint8_t network_attach_retry = 10;
static uint8_t ip_lost_retries = 10;
if ((millis() < next_loop_millis)) {
// some commands need some delay
yield();
return;
}
#ifdef USE_MODEM_POWER
if (this->power_transition_) {
if (!this->watchdog_)
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
// A power state is used to handle long tonuart/toffuart delay
switch (this->power_state_) {
case ModemPowerState::TON:
this->power_pin_->digital_write(false);
delay(USE_MODEM_POWER_TON);
this->power_pin_->digital_write(true);
next_loop_millis = millis() + USE_MODEM_POWER_TONUART; // delay for next loop
this->power_state_ = ModemPowerState::TONUART;
ESP_LOGD(TAG, "Will check that the modem is on in %.1fs...", float(USE_MODEM_POWER_TONUART) / 1000);
break;
case ModemPowerState::TONUART:
this->power_transition_ = false;
if (!this->modem_sync_()) {
ESP_LOGE(TAG, "Unable to power on the modem");
this->powered_on_ = false;
} else {
ESP_LOGI(TAG, "Modem powered ON");
this->powered_on_ = true;
this->modem_synced_ = false;
this->watchdog_.reset();
}
break;
case ModemPowerState::TOFF:
delay(10);
this->power_pin_->digital_write(false);
delay(USE_MODEM_POWER_TOFF);
this->power_pin_->digital_write(true);
this->power_state_ = ModemPowerState::TOFFUART;
ESP_LOGD(TAG, "Will check that the modem is off in %.1fs...", float(USE_MODEM_POWER_TOFFUART) / 1000);
next_loop_millis = millis() + USE_MODEM_POWER_TOFFUART; // delay for next loop
break;
case ModemPowerState::TOFFUART:
this->power_transition_ = false;
if (this->modem_ready()) {
ESP_LOGE(TAG, "Unable to power off the modem");
this->powered_on_ = true;
} else {
ESP_LOGI(TAG, "Modem powered OFF");
this->powered_on_ = false;
this->modem_synced_ = false;
this->watchdog_.reset();
}
break;
}
yield();
return;
}
#endif // USE_MODEM_POWER
switch (this->state_) {
case ModemComponentState::NOT_RESPONDING:
if (this->start_) {
if (this->modem_ready()) {
ESP_LOGI(TAG, "Modem recovered");
this->status_clear_warning();
this->state_ = ModemComponentState::DISCONNECTED;
} else {
if (!this->powered_on_) {
this->poweron_();
} else if (this->not_responding_cb_) {
if (!this->not_responding_cb_->is_action_running()) {
ESP_LOGD(TAG, "Calling 'on_not_responding' callback");
this->not_responding_cb_->trigger();
}
} else {
ESP_LOGW(TAG, "Modem not responding, and no 'on_not_responding' action defined");
}
}
}
break;
case ModemComponentState::DISCONNECTED:
if (this->enabled_) {
// be sure the modem is on and synced
if (!this->powered_on_) {
this->poweron_();
break;
} else if (!this->modem_synced_) {
if (!this->modem_sync_()) {
ESP_LOGE(TAG, "Modem not responding");
this->state_ = ModemComponentState::NOT_RESPONDING;
}
}
if (this->start_) {
// want to connect
if (!connecting) {
// wait for the modem be attached to a network, start ppp, and set connecting=true
if (!this->watchdog_)
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
if (is_network_attached_()) {
network_attach_retry = 10;
if (this->start_ppp_()) {
connecting = true;
next_loop_millis = millis() + 2000; // delay for next loop
}
} 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");
if (this->power_pin_) {
this->poweroff_();
} else {
this->state_ = ModemComponentState::NOT_RESPONDING;
}
}
next_loop_millis = millis() + 1000; // delay to retry
}
} else {
// connecting
if (!this->connected_) {
// wait until this->connected_ set to true by IP_EVENT_PPP_GOT_IP
next_loop_millis = millis() + 1000; // delay for next loop
// connecting timeout
if (millis() - this->connect_begin_ > 15000) {
ESP_LOGW(TAG, "Connecting via Modem failed! Re-connecting...");
// TODO: exit data/cmux without error check
connecting = false;
}
} else {
connecting = false;
ESP_LOGI(TAG, "Connected via Modem");
this->state_ = ModemComponentState::CONNECTED;
this->dump_connect_params_();
this->status_clear_warning();
this->watchdog_.reset();
}
}
} else {
this->start_ = true;
}
} else {
this->state_ = ModemComponentState::DISABLED;
this->watchdog_.reset();
}
break;
case ModemComponentState::CONNECTED:
if (this->enabled_) {
if (!this->connected_) {
this->status_set_warning("Connection via Modem lost!");
this->state_ = ModemComponentState::DISCONNECTED;
}
disconnecting = false;
} else {
if (this->connected_) {
// connected but disbled, so disconnect
if (!disconnecting) {
disconnecting = true;
ip_lost_retries = 10;
this->watchdog_ = std::make_shared<watchdog::WatchdogManager>(60000);
ESP_LOGD(TAG, "Disconnecting...");
this->stop_ppp_();
delay(200); // NOLINT
ESP_LOGD(TAG, "Disconnected after %.1fmin", float(this->connect_begin_) / (1000 * 60));
} else {
// disconnecting
// Waiting for IP_EVENT_PPP_LOST_IP.
// This can take a long time, so we ckeck the IP addr, and trigger the event manualy if it's null.
esp_netif_ip_info_t ip_info;
esp_netif_get_ip_info(this->ppp_netif_, &ip_info);
if (ip_info.ip.addr == 0) {
// lost IP
esp_event_post(IP_EVENT, IP_EVENT_PPP_LOST_IP, nullptr, 0, 0);
} else {
ESP_LOGD(TAG, "Waiting for lost IP... (retries %" PRIu8 ")", ip_lost_retries);
ip_lost_retries--;
if (ip_lost_retries == 0) {
// Something goes wrong, we have still an IP
ESP_LOGE(TAG, "No IP lost event recieved. Sending one manually");
esp_event_post(IP_EVENT, IP_EVENT_PPP_LOST_IP, nullptr, 0, 0);
}
}
next_loop_millis = millis() + 2000; // delay for next loop
}
} else { // if (this->connected_)
// ip lost as expected
ESP_LOGI(TAG, "PPPoS disconnected");
this->state_ = ModemComponentState::DISCONNECTED;
}
}
break;
case ModemComponentState::DISABLED:
if (this->enabled_) {
this->state_ = ModemComponentState::DISCONNECTED;
ESP_LOGE(TAG, "here");
} else if (this->powered_on_) {
this->poweroff_();
}
next_loop_millis = millis() + 2000; // delay for next loop
break;
}
if (this->state_ != last_state) {
ESP_LOGV(TAG, "State changed: %s -> %s", state_to_string(last_state).c_str(),
state_to_string(this->state_).c_str());
this->on_state_callback_.call(last_state, this->state_);
last_state = this->state_;
}
}
void ModemComponent::enable() {
ESP_LOGD(TAG, "Enabling modem");
if (this->state_ == ModemComponentState::DISABLED) {
this->state_ = ModemComponentState::DISCONNECTED;
}
this->start_ = true;
this->enabled_ = true;
}
void ModemComponent::disable() {
ESP_LOGD(TAG, "Disabling modem");
this->enabled_ = false;
if (this->state_ != ModemComponentState::CONNECTED) {
this->state_ = ModemComponentState::DISCONNECTED;
}
}
bool ModemComponent::get_power_status() {
#ifdef USE_MODEM_STATUS
// This code is not fully checked. The status pin seems to be flickering on Lilygo T-SIM7600
return this->status_pin_->digital_read();
#else
if (!this->cmux_ && this->connected_) {
// Data mode, connected: assume power is OK
return true;
}
return this->modem_ready();
#endif
}
void ModemComponent::poweron_() { void ModemComponent::poweron_() {
#ifdef USE_MODEM_POWER #ifdef USE_MODEM_POWER
this->power_state_ = ModemPowerState::TON; this->internal_state_.power_state = ModemPowerState::TON;
this->power_transition_ = true; this->internal_state_.power_transition = true;
return;
#else #else
if (this->modem_ready()) { if (this->modem_ready()) {
ESP_LOGV(TAG, "Modem is already ON"); ESP_LOGV(TAG, "Modem is already ON");
@ -638,14 +754,13 @@ void ModemComponent::poweron_() {
void ModemComponent::poweroff_() { void ModemComponent::poweroff_() {
#ifdef USE_MODEM_POWER #ifdef USE_MODEM_POWER
this->power_state_ = ModemPowerState::TOFF; this->internal_state_.power_state = ModemPowerState::TOFF;
this->power_transition_ = true; this->internal_state_.power_transition = true;
return;
#endif // USE_MODEM_POWER #endif // USE_MODEM_POWER
} }
void ModemComponent::dump_connect_params_() { void ModemComponent::dump_connect_params_() {
if (!this->connected_) { if (!this->internal_state_.connected) {
ESP_LOGCONFIG(TAG, "Modem connection: Not connected"); ESP_LOGCONFIG(TAG, "Modem connection: Not connected");
return; return;
} }
@ -666,104 +781,6 @@ void ModemComponent::dump_connect_params_() {
ESP_LOGCONFIG(TAG, " DNS fallback: %s", network::IPAddress(dns_fallback_ip).str().c_str()); ESP_LOGCONFIG(TAG, " DNS fallback: %s", network::IPAddress(dns_fallback_ip).str().c_str());
} }
bool ModemComponent::is_network_attached_() {
int network_attachment_state;
command_result err = this->dce->get_network_attachment_state(network_attachment_state);
if (err == command_result::OK) {
return network_attachment_state;
} else
return false;
}
void ModemComponent::dump_dce_status_() {
ESP_LOGCONFIG(TAG, "Modem status:");
command_result err;
std::string result;
err = this->dce->get_module_name(result);
if (err != command_result::OK) {
result = "command " + command_result_to_string(err);
}
ESP_LOGCONFIG(TAG, " Module name : %s", result.c_str());
int rssi;
int ber;
err = this->dce->get_signal_quality(rssi, ber);
if (err != command_result::OK) {
result = "command " + command_result_to_string(err);
} else {
float ber_f;
if (ber != 99) {
ber_f = float(ber);
} else
ber_f = {};
std::ostringstream oss;
oss << "rssi " << rssi << "(" << (rssi * 100) / 31 << "%), ber " << ber << "(" << (ber_f * 100) / 7 << "%)";
result = oss.str();
}
ESP_LOGCONFIG(TAG, " Signal quality : %s", result.c_str());
int network_attachment_state;
err = this->dce->get_network_attachment_state(network_attachment_state);
if (err == command_result::OK) {
result = network_attachment_state ? "Yes" : "No";
} else {
result = "command " + command_result_to_string(err);
}
ESP_LOGCONFIG(TAG, " Attached to network: %s", result.c_str());
}
std::string ModemComponent::send_at(const std::string &cmd) {
std::string result;
command_result status;
ESP_LOGV(TAG, "Sending command: %s", cmd.c_str());
status = this->dce->at(cmd, result, this->command_delay_);
ESP_LOGV(TAG, "Result for command %s: %s (status %s)", cmd.c_str(), result.c_str(),
command_result_to_string(status).c_str());
if (status != esp_modem::command_result::OK) {
result = "ERROR";
}
return result;
}
bool ModemComponent::get_imei(std::string &result) {
// wrapper around this->dce->get_imei() that check that the result is valid
// (so it can be used to check if the modem is responding correctly (a simple 'AT' cmd is sometime not enough))
command_result status;
status = this->dce->get_imei(result);
bool success = true;
if (status == command_result::OK && result.length() == 15) {
for (char c : result) {
if (!isdigit(static_cast<unsigned char>(c))) {
success = false;
break;
}
}
} else {
success = false;
}
if (!success) {
result = "UNAVAILABLE";
}
return success;
}
bool ModemComponent::modem_ready() {
// check if the modem is ready to answer AT commands
std::string imei;
if (this->get_imei(imei)) {
// we are sure that the modem is on
this->powered_on_ = true;
return true;
}
return false;
}
void ModemComponent::add_on_state_callback(std::function<void(ModemComponentState, ModemComponentState)> &&callback) {
this->on_state_callback_.add(std::move(callback));
}
} // namespace modem } // namespace modem
} // namespace esphome } // namespace esphome

View file

@ -49,16 +49,7 @@ enum class ModemPowerState {
class ModemComponent : public Component { class ModemComponent : public Component {
public: public:
ModemComponent(); void set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
void setup() override;
void loop() override;
void dump_config() override;
bool is_connected();
float get_setup_priority() const override;
bool can_proceed() override;
network::IPAddresses get_ip_addresses();
std::string get_use_address() const;
void set_use_address(const std::string &use_address);
void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; } void set_rx_pin(InternalGPIOPin *rx_pin) { this->rx_pin_ = rx_pin; }
void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; } void set_tx_pin(InternalGPIOPin *tx_pin) { this->tx_pin_ = tx_pin; }
void set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; } void set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; }
@ -70,13 +61,38 @@ class ModemComponent : public Component {
void set_not_responding_cb(Trigger<> *not_responding_cb) { this->not_responding_cb_ = not_responding_cb; } void set_not_responding_cb(Trigger<> *not_responding_cb) { this->not_responding_cb_ = not_responding_cb; }
void enable_cmux() { this->cmux_ = true; } void enable_cmux() { this->cmux_ = true; }
void add_init_at_command(const std::string &cmd) { this->init_at_commands_.push_back(cmd); } 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; }
std::string send_at(const std::string &cmd); std::string send_at(const std::string &cmd);
bool get_imei(std::string &result); bool get_imei(std::string &result);
bool get_power_status(); bool get_power_status();
bool modem_ready(); bool modem_ready();
void enable(); void enable();
void disable(); void disable();
void add_on_state_callback(std::function<void(ModemComponentState, ModemComponentState)> &&callback);
network::IPAddresses get_ip_addresses();
std::string get_use_address() const;
void dump_dce_status();
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
ModemComponent();
void setup() override;
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();
};
void add_on_state_callback(std::function<void(ModemComponentState, ModemComponentState)> &&callback) {
this->on_state_callback_.add(std::move(callback));
}
// main esp_modem object
// https://docs.espressif.com/projects/esp-protocols/esp_modem/docs/latest/internal_docs.html#dce-internal-implementation
std::unique_ptr<DCE> dce{nullptr}; std::unique_ptr<DCE> dce{nullptr};
protected: protected:
@ -91,7 +107,8 @@ class ModemComponent : public Component {
void poweroff_(); void poweroff_();
static void ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
void dump_connect_params_(); void dump_connect_params_();
void dump_dce_status_();
// Attributes from yaml config
InternalGPIOPin *tx_pin_; InternalGPIOPin *tx_pin_;
InternalGPIOPin *rx_pin_; InternalGPIOPin *rx_pin_;
GPIOPin *status_pin_{nullptr}; GPIOPin *status_pin_{nullptr};
@ -101,38 +118,50 @@ class ModemComponent : public Component {
std::string password_; std::string password_;
std::string apn_; std::string apn_;
std::vector<std::string> init_at_commands_; std::vector<std::string> init_at_commands_;
std::string use_address_;
bool cmux_{false};
// separate handler for `on_not_responding` (we want to know when it's ended)
Trigger<> *not_responding_cb_{nullptr};
CallbackManager<void(ModemComponentState, ModemComponentState)> on_state_callback_;
// Allow changes from yaml ?
size_t uart_rx_buffer_size_ = 2048; // 256-2048 size_t uart_rx_buffer_size_ = 2048; // 256-2048
size_t uart_tx_buffer_size_ = 1024; // 256-2048 size_t uart_tx_buffer_size_ = 1024; // 256-2048
uint8_t uart_event_queue_size_ = 30; // 10-40 uint8_t uart_event_queue_size_ = 30; // 10-40
size_t uart_event_task_stack_size_ = 2048; // 2000-6000 size_t uart_event_task_stack_size_ = 2048; // 2000-6000
uint8_t uart_event_task_priority_ = 5; // 3-22 uint8_t uart_event_task_priority_ = 5; // 3-22
uint32_t command_delay_ = 500; // timeout for AT commands
// Changes will trigger user callback
ModemComponentState component_state_{ModemComponentState::DISABLED};
// the uart DTE
// https://docs.espressif.com/projects/esp-protocols/esp_modem/docs/latest/internal_docs.html#_CPPv4N9esp_modem3DCEE
std::shared_ptr<DTE> dte_{nullptr}; std::shared_ptr<DTE> dte_{nullptr};
esp_netif_t *ppp_netif_{nullptr}; esp_netif_t *ppp_netif_{nullptr};
ModemComponentState state_{ModemComponentState::DISABLED};
// Many operation blocks a long time.
std::shared_ptr<watchdog::WatchdogManager> watchdog_; std::shared_ptr<watchdog::WatchdogManager> watchdog_;
bool cmux_{false};
bool start_{false}; struct InternalState {
bool enabled_{false}; bool start{false};
bool connected_{false}; bool enabled{false};
bool got_ipv4_address_{false}; bool connected{false};
bool got_ipv4_address{false};
// true if modem_sync_ was sucessfull // true if modem_sync_ was sucessfull
bool modem_synced_{false}; bool modem_synced{false};
// date start (millis()) // date start (millis())
uint32_t connect_begin_; uint32_t connect_begin;
std::string use_address_;
// timeout for AT commands
uint32_t command_delay_ = 500;
// guess power state // guess power state
bool powered_on_{false}; bool powered_on{false};
#ifdef USE_MODEM_POWER #ifdef USE_MODEM_POWER
// Will be true when power transitionning // Will be true when power transitionning
bool power_transition_{false}; bool power_transition{false};
// states for triggering on/off signals // states for triggering on/off signals
ModemPowerState power_state_{ModemPowerState::TOFFUART}; ModemPowerState power_state{ModemPowerState::TOFFUART};
#endif // USE_MODEM_POWER #endif // USE_MODEM_POWER
// separate handler for `on_not_responding` (we want to know when it's ended) };
Trigger<> *not_responding_cb_{nullptr}; InternalState internal_state_;
CallbackManager<void(ModemComponentState, ModemComponentState)> on_state_callback_;
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)