baud rate

This commit is contained in:
oarcher 2024-08-17 02:45:34 +02:00
parent def2be4b83
commit 8681a5b302
4 changed files with 155 additions and 102 deletions

View file

@ -7,6 +7,7 @@ from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
# from esphome.components.wifi import wifi_has_sta # uncomment after PR#4091 merged
import esphome.config_validation as cv
from esphome.const import (
CONF_BAUD_RATE,
CONF_DEBUG,
CONF_ENABLE_ON_BOOT,
CONF_ID,
@ -79,6 +80,7 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_ID): cv.declare_id(ModemComponent),
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_RX_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_BAUD_RATE): cv.positive_int,
cv.Required(CONF_MODEL): cv.one_of(*MODEM_MODELS, upper=True),
cv.Required(CONF_APN): cv.string,
cv.Optional(CONF_STATUS_PIN): pins.gpio_input_pin_schema,
@ -166,8 +168,8 @@ async def to_code(config):
# If Uart queue full message ( A7672 ), those config option might be changed
# https://github.com/espressif/esp-protocols/issues/272#issuecomment-1558682967
add_idf_sdkconfig_option("CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD", True)
add_idf_sdkconfig_option("ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED", True)
add_idf_sdkconfig_option("ESP_MODEM_CMUX_USE_SHORT_PAYLOADS_ONLY", False)
add_idf_sdkconfig_option("CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED", True)
add_idf_sdkconfig_option("CONFIG_ESP_MODEM_CMUX_USE_SHORT_PAYLOADS_ONLY", False)
cg.add_define("USE_MODEM")
@ -218,6 +220,9 @@ async def to_code(config):
rx_pin = await cg.gpio_pin_expression(config[CONF_RX_PIN])
cg.add(var.set_rx_pin(rx_pin))
if baud_rate := config.get(CONF_BAUD_RATE, None):
cg.add(var.set_baud_rate(baud_rate))
if status_pin := config.get(CONF_STATUS_PIN, None):
pin = await cg.gpio_pin_expression(status_pin)
cg.add(var.set_status_pin(pin))

View file

@ -48,11 +48,7 @@ ModemComponent::ModemComponent() {
global_modem_component = this;
}
void ModemComponent::enable_debug() {
esp_log_level_set("command_lib", ESP_LOG_VERBOSE);
// esp_log_level_set("CMUX", ESP_LOG_VERBOSE);
// esp_log_level_set("CMUX Received", ESP_LOG_VERBOSE);
}
void ModemComponent::enable_debug() { esp_log_level_set("command_lib", ESP_LOG_VERBOSE); }
bool ModemComponent::is_modem_connected(bool verbose) {
float rssi, ber;
@ -98,6 +94,8 @@ AtCommandResult ModemComponent::get_imei() {
} else {
at_command_result.success = false;
}
ESP_LOGV(TAG, "imei: %s (status: %s)", at_command_result.c_str(),
command_result_to_string(at_command_result.esp_modem_command_result).c_str());
return at_command_result;
}
@ -114,25 +112,28 @@ bool ModemComponent::get_power_status() {
#endif
}
bool ModemComponent::sync() {
this->internal_state_.modem_synced = this->get_imei();
if (this->internal_state_.modem_synced)
this->internal_state_.powered_on = true;
return this->internal_state_.modem_synced;
}
bool ModemComponent::modem_ready(bool force_check) {
// check if the modem is ready to answer AT commands
// We first try to check flags, and then really send an AT command if force_check
if (!this->dce)
return false;
if (!this->internal_state_.modem_synced)
return false;
if (!this->cmux_ && this->internal_state_.connected)
return false;
if (!this->internal_state_.powered_on)
return false;
#ifdef USE_MODEM_POWER
if (this->internal_state_.power_transition)
return false;
#endif
if (force_check) {
if (this->get_imei()) {
if (this->sync()) {
// we are sure that the modem is on
this->internal_state_.powered_on = true;
return true;
@ -142,8 +143,6 @@ bool ModemComponent::modem_ready(bool force_check) {
return true;
}
bool ModemComponent::modem_ready() { return this->modem_ready(false); }
void ModemComponent::enable() {
ESP_LOGD(TAG, "Enabling modem");
if (this->component_state_ == ModemComponentState::DISABLED) {
@ -259,7 +258,7 @@ void ModemComponent::setup() {
nullptr);
ESPHL_ERROR_CHECK(err, "IP event handler register error");
this->modem_lazy_init_();
this->modem_create_dce_dte_(); // real init will be done by enable
ESP_LOGV(TAG, "Setup finished");
}
@ -291,7 +290,7 @@ void ModemComponent::loop() {
break;
case ModemPowerState::TONUART:
ESP_LOGD(TAG, "TONUART check sync");
if (!this->modem_sync_()) {
if (!this->modem_init_()) {
ESP_LOGE(TAG, "Unable to power on the modem");
} else {
ESP_LOGI(TAG, "Modem powered ON");
@ -329,8 +328,8 @@ void ModemComponent::loop() {
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_()) {
// this->modem_create_dce_dte_();
if (!this->modem_init_()) {
ESP_LOGE(TAG, "Unable to recover modem");
} else {
this->component_state_ = ModemComponentState::DISCONNECTED;
@ -345,7 +344,7 @@ void ModemComponent::loop() {
this->poweron_();
break;
} else if (!this->internal_state_.modem_synced) {
if (!this->modem_sync_()) {
if (!this->modem_init_()) {
this->component_state_ = ModemComponentState::NOT_RESPONDING;
}
}
@ -443,15 +442,12 @@ void ModemComponent::loop() {
}
}
void ModemComponent::modem_lazy_init_() {
// destroy previous dte/dce, and recreate them.
void ModemComponent::modem_create_dce_dte_(int baud_rate) {
// create or recreate dte and dce.
// no communication is done with the modem.
this->internal_state_.modem_synced = false;
this->dte_.reset();
this->dce.reset();
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
dte_config.uart_config.tx_io_num = this->tx_pin_->get_pin();
@ -459,10 +455,16 @@ void ModemComponent::modem_lazy_init_() {
dte_config.uart_config.rx_buffer_size = this->uart_rx_buffer_size_;
dte_config.uart_config.tx_buffer_size = this->uart_tx_buffer_size_;
dte_config.uart_config.event_queue_size = this->uart_event_queue_size_;
if (baud_rate != 0) {
dte_config.uart_config.baud_rate = baud_rate;
this->internal_state_.baud_rate_changed = true;
}
dte_config.task_stack_size = this->uart_event_task_stack_size_;
dte_config.task_priority = this->uart_event_task_priority_;
dte_config.dte_buffer_size = this->uart_rx_buffer_size_ / 2;
this->dce.reset();
this->dte_.reset();
this->dte_ = create_uart_dte(&dte_config);
if (!this->dte_->set_mode(modem_mode::COMMAND_MODE)) {
@ -497,104 +499,132 @@ void ModemComponent::modem_lazy_init_() {
ESP_LOGV(TAG, "DTE and CDE created");
}
bool ModemComponent::modem_sync_() {
// force command mode, check sim, and send init_at commands
// close cmux/data if needed, and may reboot the modem.
bool ModemComponent::modem_preinit_() {
// init the modem to get command mode.
// if baud_rate != 0, will also set the baud rate.
uint32_t start_ms = millis();
uint32_t elapsed_ms;
std::string result;
// std::string result;
ESP_LOGV(TAG, "Checking if the modem is synced...");
ESP_LOGV(TAG, "Checking if the modem is reachable...");
this->flush_uart_();
bool status = this->get_imei();
if (!status) {
bool success = this->sync();
if (!success) {
// the modem is not responding. possible causes are:
// - warm reboot, it's still in data or cmux mode.
// - has a non default baud rate
// - power off
uint32_t start_ms = millis();
uint32_t elapsed_ms;
// Try to exit CMUX_MANUAL_DATA or DATA_MODE, if any
ESP_LOGD(TAG, "Connecting to the the modem...");
watchdog::WatchdogManager wdt(30000);
// huge watchdog, because some commands are blocking for a very long time.
watchdog::WatchdogManager wdt(60000);
auto command_mode = [this]() -> bool {
ESP_LOGVV(TAG, "trying command mode");
ESP_LOGV(TAG, "trying command mode");
this->dce->set_mode(modem_mode::UNDEF);
return this->dce->set_mode(modem_mode::COMMAND_MODE) && this->get_imei();
return this->dce->set_mode(modem_mode::COMMAND_MODE) && this->sync();
};
auto cmux_command_mode = [this]() -> bool {
ESP_LOGVV(TAG, "trying cmux command mode");
ESP_LOGV(TAG, "trying cmux command mode");
return this->dce->set_mode(modem_mode::CMUX_MANUAL_MODE) &&
this->dce->set_mode(modem_mode::CMUX_MANUAL_COMMAND) && this->get_imei();
this->dce->set_mode(modem_mode::CMUX_MANUAL_COMMAND) && this->sync();
};
// The cmux state is supposed to be the same before the reboot. But if it has changed (new firwmare), we will try
// to fallback to inverted cmux state.
if (this->cmux_) {
status = cmux_command_mode() || (command_mode() && cmux_command_mode());
success = cmux_command_mode() || (command_mode() && cmux_command_mode());
} else {
status = command_mode() || (cmux_command_mode() && command_mode());
success = command_mode() || (cmux_command_mode() && command_mode());
}
elapsed_ms = millis() - start_ms;
if (!status) {
ESP_LOGW(TAG, "modem not responding after %" PRIu32 "ms.", elapsed_ms);
// assume the modem is OFF
this->internal_state_.powered_on = false;
} else {
if (success) {
ESP_LOGD(TAG, "Connected to the modem in %" PRIu32 "ms", elapsed_ms);
this->internal_state_.powered_on = true;
}
} else {
// modem responded without need to recover command mode
ESP_LOGD(TAG, "Modem already synced");
this->internal_state_.powered_on = true;
}
if (status && !this->internal_state_.modem_synced) {
this->internal_state_.modem_synced = true;
// First time the modem is synced, or modem recovered
App.feed_wdt();
watchdog::WatchdogManager wdt(30000);
delay(2000); // NOLINT
if (!this->get_imei()) {
ESP_LOGW(TAG, "Unable to sync modem");
} else if ((this->baud_rate_ != 0) && !this->internal_state_.baud_rate_changed) {
ESP_LOGD(TAG, "Failed to connected to the modem in %" PRIu32 "ms", elapsed_ms);
ESP_LOGD(TAG, "Retrying with baud rate %d", this->baud_rate_);
this->modem_create_dce_dte_(this->baud_rate_);
return this->modem_preinit_(); // recursive call, but only 1 depth
} else {
ESP_LOGE(TAG, "Fatal: modem not responding during init");
return false;
}
this->send_init_at_();
if (!this->pin_code_.empty()) {
if (!this->prepare_sim_()) {
// fatal error
this->disable();
status = false;
}
} else {
ESP_LOGI(TAG, "No pin_code, so no pin check");
}
ESP_LOGI(TAG, "Modem infos:");
std::string result;
ESPMODEM_ERROR_CHECK(this->dce->get_module_name(result), "get_module_name");
ESP_LOGI(TAG, " Module name: %s", result.c_str());
}
this->internal_state_.modem_synced = status;
// modem synced
ESP_LOGVV(TAG, "Sync end status: %d", this->internal_state_.modem_synced);
if ((this->baud_rate_ != 0) && !this->internal_state_.baud_rate_changed) {
ESP_LOGD(TAG, "Setting baud rate: %d", this->baud_rate_);
this->dce->set_baud(this->baud_rate_);
delay(1000);
this->flush_uart_();
// need to recreate dte/dce with new baud rate
this->modem_create_dce_dte_(this->baud_rate_);
this->flush_uart_();
if (this->sync()) {
ESP_LOGI(TAG, "Modem baud rate set to %d", this->baud_rate_);
success = true;
} else {
// not able to switch to new baud rate. reset to default
this->internal_state_.baud_rate_changed = false;
this->modem_create_dce_dte_();
success = false;
}
}
this->internal_state_.modem_synced = success;
this->internal_state_.powered_on = success;
return success;
}
return status;
bool ModemComponent::modem_init_() {
// force command mode, check sim, and send init_at commands
// close cmux/data if needed, and may reboot the modem.
bool success = this->modem_preinit_() && this->sync();
if (!success) {
ESP_LOGE(TAG, "Fatal: modem not responding");
return false;
}
this->send_init_at_();
if (!this->pin_code_.empty()) {
if (!this->prepare_sim_()) {
ESP_LOGE(TAG, "Fatal: Sim error");
return false;
}
} else {
ESP_LOGI(TAG, "No pin_code, so no pin check");
}
ESP_LOGI(TAG, "Modem infos:");
std::string result;
ESPMODEM_ERROR_CHECK(this->dce->get_module_name(result), "get_module_name");
ESP_LOGI(TAG, " Module name: %s", result.c_str());
success = this->sync();
if (!success) {
ESP_LOGE(TAG, "Fatal: unable to init modem");
}
return success;
}
bool ModemComponent::prepare_sim_() {
command_result modem_status;
std::string output;
// this->dce->read_pin(pin_ok) // not used, because we can't know the cause of the error.
modem_status = this->dce->command(
this->dce->command(
"AT+CPIN?\r",
[&](uint8_t *data, size_t len) {
output.assign(reinterpret_cast<char *>(data), len);
@ -605,11 +635,11 @@ bool ModemComponent::prepare_sim_() {
ESP_LOGD(TAG, "SIM: %s", output.c_str());
if (output.find("+CPIN: READY") != std::string::npos) {
if ((output.find("+CPIN: READY") != std::string::npos) || (output.find("+CPIN: SIM PIN") != std::string::npos)) {
return true; // pin not needed or already unlocked
} else {
if (output.find("SIM not inserted") != std::string::npos) {
this->abort_("Sim card not inserted.");
return false;
}
}
@ -625,14 +655,22 @@ void ModemComponent::send_init_at_() {
// send initial AT commands from yaml
for (const auto &cmd : this->init_at_commands_) {
App.feed_wdt();
auto at_command_result = this->send_at(cmd);
if (!at_command_result) {
ESP_LOGE(TAG, "Error while executing 'init_at' '%s' command", cmd.c_str());
} else {
ESP_LOGI(TAG, "'init_at' '%s' output: %s", cmd.c_str(), at_command_result.output.c_str());
}
delay(2000); // NOLINT
std::string output;
ESPMODEM_ERROR_CHECK(this->dce->command(
cmd + "\r",
[&](uint8_t *data, size_t len) {
output.assign(reinterpret_cast<char *>(data), len);
std::replace(output.begin(), output.end(), '\n', ' ');
return command_result::OK;
},
2000),
"init_at");
ESP_LOGI(TAG, "init_at %s: %s", cmd.c_str(), output.c_str());
}
this->flush_uart_();
}
bool ModemComponent::is_network_attached_() {
@ -739,7 +777,7 @@ 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");
// this->send_at("AT+CFUN=1,1");
App.reboot();
}
@ -767,18 +805,21 @@ void ModemComponent::dump_connect_params_() {
bool ModemComponent::flush_uart_() {
size_t cleaned = 0;
std::string output;
this->dce->command(
"",
[&](uint8_t *data, size_t len) {
cleaned = len;
output.assign(reinterpret_cast<char *>(data), len);
std::replace(output.begin(), output.end(), '\n', ' ');
return command_result::OK;
},
1000) == command_result::OK;
2000);
if (cleaned != 0) {
ESP_LOGW(TAG, "Cleaned %d modem buffer data", cleaned);
ESP_LOGW(TAG, "Cleaned %d modem buffer data: %s", cleaned, output.c_str());
}
return cleaned != 0;
}
const char *AtCommandResult::c_str() const {

View file

@ -58,6 +58,7 @@ class ModemComponent : public Component {
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; }
void set_baud_rate(int baud_rate) { this->baud_rate_ = baud_rate; }
void set_model(const std::string &model) { this->model_ = model; }
void set_power_pin(GPIOPin *power_pin) { this->power_pin_ = power_pin; }
void set_power_ton(int ton) { this->power_ton_ = ton; }
@ -78,7 +79,8 @@ class ModemComponent : public Component {
AtCommandResult send_at(const std::string &cmd, uint32_t timeout);
AtCommandResult get_imei();
bool get_power_status();
bool modem_ready();
bool sync();
bool modem_ready() { return this->modem_ready(false); }
bool modem_ready(bool force_check);
void enable();
void disable();
@ -105,8 +107,10 @@ class ModemComponent : public Component {
std::unique_ptr<DCE> dce{nullptr};
protected:
void modem_lazy_init_();
bool modem_sync_();
void modem_create_dce_dte_(int baud_rate);
void modem_create_dce_dte_() { this->modem_create_dce_dte_(0); }
bool modem_preinit_();
bool modem_init_();
bool prepare_sim_();
void send_init_at_();
bool is_network_attached_();
@ -147,6 +151,7 @@ class ModemComponent : public Component {
uint8_t uart_event_task_priority_ = 5; // 3-22
uint32_t command_delay_ = 1000; // timeout for AT commands
uint32_t reconnect_grace_period_ = 30000; // let some time to mqtt or api to reconnect before retry
int baud_rate_ = 0;
// Changes will trigger user callback
ModemComponentState component_state_{ModemComponentState::DISABLED};
@ -163,7 +168,7 @@ class ModemComponent : public Component {
bool enabled{false};
bool connected{false};
bool got_ipv4_address{false};
// true if modem_sync_ was sucessfull
// true if modem_init_ was sucessfull
bool modem_synced{false};
// date start (millis())
uint32_t connect_begin;
@ -175,6 +180,7 @@ class ModemComponent : public Component {
ModemPowerState power_state{ModemPowerState::TOFFUART};
// ask the modem to reconnect
bool reconnect{false};
bool baud_rate_changed{false};
};
InternalState internal_state_;
};

View file

@ -4,6 +4,7 @@ modem:
id: atmodem
rx_pin: GPIO26
tx_pin: GPIO27
baud_rate: 115200
power_pin:
number: GPIO04
inverted: True