diff --git a/esphome/components/modem/__init__.py b/esphome/components/modem/__init__.py index 1d68eb6e7b..7874660008 100644 --- a/esphome/components/modem/__init__.py +++ b/esphome/components/modem/__init__.py @@ -31,6 +31,14 @@ CONF_POWER_PIN = "power_pin" CONF_INIT_AT = "init_at" CONF_ON_NOT_RESPONDING = "on_not_responding" +MODEM_MODELS = ["BG96", "SIM800", "SIM7000", "SIM7600", "GENERIC"] +MODEM_MODELS_POWER = { + "BG96": {"ton": 600, "tonuart": 4900, "toff": 650, "toffuart": 2000}, + "SIM800": {"ton": 1300, "tonuart": 3000, "toff": 200, "toffuart": 3000}, + "SIM7000": {"ton": 1100, "tonuart": 4500, "toff": 1300, "toffuart": 1800}, + "SIM7600": {"ton": 500, "tonuart": 12000, "toff": 2800, "toffuart": 25000}, +} + modem_ns = cg.esphome_ns.namespace("modem") ModemComponent = modem_ns.class_("ModemComponent", cg.Component) ModemComponentState = modem_ns.enum("ModemComponentState") @@ -51,7 +59,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(): 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.Required(CONF_MODEL): cv.string, + cv.Required(CONF_MODEL): cv.one_of(*MODEM_MODELS, upper=True), cv.Required(CONF_APN): cv.string, cv.Optional(CONF_STATUS_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_schema, @@ -87,6 +95,21 @@ CONFIG_SCHEMA = cv.All( ) +def _final_validate(config): + if config.get(CONF_POWER_PIN, None) and not config.get(CONF_STATUS_PIN, None): + raise cv.Invalid( + f"'{CONF_STATUS_PIN}' must be declared if using '{CONF_POWER_PIN}'" + ) + if config.get(CONF_POWER_PIN, None): + if config[CONF_MODEL] not in MODEM_MODELS_POWER: + raise cv.Invalid( + f"Modem model '{config[CONF_MODEL]}' has no power power specs." + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + @coroutine_with_priority(60.0) async def to_code(config): add_idf_component( @@ -135,7 +158,15 @@ async def to_code(config): for cmd in init_at: cg.add(var.add_init_at_command(cmd)) - cg.add(var.set_model(config[CONF_MODEL])) + modem_model = config[CONF_MODEL] + cg.add_define("USE_MODEM_MODEL", modem_model) + cg.add_define(f"USE_MODEM_MODEL_{modem_model}") + + if power_spec := MODEM_MODELS_POWER.get(modem_model, None): + cg.add_define("USE_MODEM_POWER") + for spec, value in power_spec.items(): + cg.add_define(f"USE_MODEM_POWER_{spec.upper()}", value) + cg.add(var.set_apn(config[CONF_APN])) tx_pin = await cg.gpio_pin_expression(config[CONF_TX_PIN]) @@ -147,6 +178,7 @@ async def to_code(config): if status_pin := config.get(CONF_STATUS_PIN, None): pin = await cg.gpio_pin_expression(status_pin) cg.add(var.set_status_pin(pin)) + cg.add_define("USE_MODEM_STATUS") if power_pin := config.get(CONF_POWER_PIN, None): pin = await cg.gpio_pin_expression(power_pin) diff --git a/esphome/components/modem/modem_component.cpp b/esphome/components/modem/modem_component.cpp index 0c034f0c80..a677f2b661 100644 --- a/esphome/components/modem/modem_component.cpp +++ b/esphome/components/modem/modem_component.cpp @@ -50,7 +50,22 @@ ModemComponent::ModemComponent() { global_modem_component = this; } -void ModemComponent::dump_config() { ESP_LOGCONFIG(TAG, "Config Modem:"); } +void ModemComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Config Modem:"); + ESP_LOGCONFIG(TAG, " Model : %s", USE_MODEM_MODEL); + ESP_LOGCONFIG(TAG, " APN : %s", this->apn_.c_str()); + ESP_LOGCONFIG(TAG, " PIN code : %s", (this->pin_code_.empty()) ? "No" : "Yes (not shown)"); + ESP_LOGCONFIG(TAG, " Tx Pin : GPIO%u", this->tx_pin_->get_pin()); + ESP_LOGCONFIG(TAG, " Rx Pin : GPIO%u", this->rx_pin_->get_pin()); + ESP_LOGCONFIG(TAG, " Power pin : %s", + (this->power_pin_) ? ("GPIO" + std::to_string(this->power_pin_->get_pin())).c_str() : "Not defined"); + if (this->status_pin_) { + std::string current_status = this->get_power_status() ? "ON" : "OFF"; + ESP_LOGCONFIG(TAG, " Status pin: GPIO%u (current state %s)", this->status_pin_->get_pin(), current_status.c_str()); + } else { + ESP_LOGCONFIG(TAG, " Status pin: Not defined"); + } +} float ModemComponent::get_setup_priority() const { return setup_priority::WIFI; } @@ -153,33 +168,21 @@ void ModemComponent::reset_() { this->dte_ = create_uart_dte(&this->dte_config_); - assert(this->dte_); - ESP_LOGV(TAG, "DCE setup"); - // NOLINTBEGIN(bugprone-branch-clone) - // ( because create_modem_dce(dce_factory::ModemType, config, std::move(dte), netif) is private ) - switch (this->model_) { - case ModemModel::BG96: - this->dce = create_BG96_dce(&this->dce_config_, this->dte_, this->ppp_netif_); - break; - case ModemModel::SIM800: - this->dce = create_SIM800_dce(&this->dce_config_, this->dte_, this->ppp_netif_); - break; - case ModemModel::SIM7000: - this->dce = create_SIM7000_dce(&this->dce_config_, this->dte_, this->ppp_netif_); - break; - case ModemModel::SIM7600: - this->dce = create_SIM7600_dce(&this->dce_config_, this->dte_, this->ppp_netif_); - break; - default: - ESP_LOGE(TAG, "Unknown modem model"); - return; - break; - } - // NOLINTEND(bugprone-branch-clone) - - assert(this->dce); +#if defined(USE_MODEM_MODEL_GENERIC) + this->dce = create_generic_dce(&this->dce_config_, this->dte_, this->ppp_netif_); +#elif defined(USE_MODEM_MODEL_BG96) + this->dce = create_BG96_dce(&this->dce_config_, this->dte_, this->ppp_netif_); +#elif defined(USE_MODEM_MODEL_SIM800) + this->dce = create_SIM800_dce(&this->dce_config_, this->dte_, this->ppp_netif_); +#elif defined(USE_MODEM_MODEL_SIM7000) + this->dce = create_SIM7000_dce(&this->dce_config_, this->dte_, this->ppp_netif_); +#elif defined(USE_MODEM_MODEL_SIM7600) + this->dce = create_SIM7600_dce(&this->dce_config_, this->dte_, this->ppp_netif_); +#else +#error Modem model not known +#endif // flow control not fully implemented, but kept here for future work if (this->dte_config_.uart_config.flow_control == ESP_MODEM_FLOW_CONTROL_HW) { @@ -346,6 +349,7 @@ void ModemComponent::loop() { this->state_ = ModemComponentState::DISCONNECTED; } break; + case ModemComponentState::CONNECTED: if (!this->start_) { this->state_ = ModemComponentState::DISCONNECTED; @@ -363,7 +367,7 @@ void ModemComponent::loop() { if (!this->modem_ready()) { ESP_LOGE(TAG, "modem not ready after hang up"); } - this->set_timeout("wait_lost_ip", 60000, [this]() { + this->set_timeout("wait_lost_ip", 15000, [this]() { // often reached on 7600, but not reached on 7670 ESP_LOGW(TAG, "No lost ip event received. Forcing disconnect state"); @@ -442,10 +446,7 @@ void ModemComponent::exit_cmux_() { } bool ModemComponent::get_power_status() { - if (!this->status_pin_) { - ESP_LOGV(TAG, "No status pin, assuming the modem is ON"); - return true; - } +#ifdef USE_MODEM_STATUS bool init_status = this->status_pin_->digital_read(); // The status pin might be floating when supposed to be low, at least on lilygo tsim7600 // as GPIO34 doesn't support pullup, we have to debounce it manually @@ -458,21 +459,25 @@ bool ModemComponent::get_power_status() { // ESP_LOGV(TAG, "Floating status pin detected for state %d", final_status); } return final_status; +#else + // No status pin, assuming the modem is ON + return true; +#endif } void ModemComponent::poweron_() { +#ifdef USE_MODEM_POWER if (this->power_pin_) { Watchdog wdt(60); ESP_LOGV(TAG, "Powering up modem with power_pin..."); this->power_transition_ = true; this->power_pin_->digital_write(false); // min 100 for SIM7600, but min 1200 for SIM800. min BG96: 650 - delay(this->modem_model_ton_[this->model_]); // NOLINT + delay(USE_MODEM_POWER_TON); this->power_pin_->digital_write(true); // use a timout for long wait delay - uint32_t tonuart = this->modem_model_tonuart_[this->model_]; - ESP_LOGD(TAG, "Will check that the modem is on in %.1fs...", float(tonuart) / 1000); - this->set_timeout("wait_poweron", tonuart, [this]() { + ESP_LOGD(TAG, "Will check that the modem is on in %.1fs...", float(USE_MODEM_POWER_TONUART) / 1000); + this->set_timeout("wait_poweron", USE_MODEM_POWER_TONUART, [this]() { Watchdog wdt(60); while (!this->get_power_status()) { delay(this->command_delay_); @@ -487,9 +492,11 @@ void ModemComponent::poweron_() { this->power_transition_ = false; }); } +#endif // USE_MODEM_POWER } void ModemComponent::poweroff_() { +#ifdef USE_MODEM_POWER if (this->get_power_status()) { if (this->power_pin_) { ESP_LOGV(TAG, "Powering off modem with power pin..."); @@ -498,12 +505,11 @@ void ModemComponent::poweroff_() { this->power_pin_->digital_write(true); delay(10); this->power_pin_->digital_write(false); - delay(this->modem_model_toff_[this->model_]); + delay(USE_MODEM_POWER_TOFF); this->power_pin_->digital_write(true); - uint32_t toffuart = this->modem_model_toffuart_[this->model_]; - ESP_LOGD(TAG, "Will check that the modem is off in %.1fs...", float(toffuart) / 1000); - this->set_timeout("wait_poweron", toffuart, [this]() { + ESP_LOGD(TAG, "Will check that the modem is off in %.1fs...", float(USE_MODEM_POWER_TOFFUART) / 1000); + this->set_timeout("wait_poweron", USE_MODEM_POWER_TOFFUART, [this]() { Watchdog wdt(60); while (this->get_power_status()) { @@ -513,10 +519,8 @@ void ModemComponent::poweroff_() { this->power_transition_ = false; }); } - } else { - ESP_LOGD(TAG, "Modem poweroff with AT command"); - this->dce->power_down(); } +#endif // USE_MODEM_POWER } void ModemComponent::dump_connect_params_() { diff --git a/esphome/components/modem/modem_component.h b/esphome/components/modem/modem_component.h index 5c55c528f0..8ab568e521 100644 --- a/esphome/components/modem/modem_component.h +++ b/esphome/components/modem/modem_component.h @@ -40,8 +40,6 @@ enum class ModemComponentState { DISABLED, }; -enum class ModemModel { BG96, SIM800, SIM7000, SIM7070, SIM7600, UNKNOWN }; - class ModemComponent : public Component { public: ModemComponent(); @@ -62,9 +60,6 @@ class ModemComponent : public Component { void set_password(const std::string &password) { this->password_ = password; } void set_pin_code(const std::string &pin_code) { this->pin_code_ = pin_code; } void set_apn(const std::string &apn) { this->apn_ = apn; } - void set_model(const std::string &model) { - this->model_ = this->modem_model_map_.count(model) ? modem_model_map_[model] : ModemModel::UNKNOWN; - } void set_not_responding_cb(Trigger<> *not_responding_cb) { this->not_responding_cb_ = not_responding_cb; } void add_init_at_command(const std::string &cmd) { this->init_at_commands_.push_back(cmd); } std::string send_at(const std::string &cmd); @@ -95,12 +90,6 @@ class ModemComponent : public Component { std::string password_; std::string apn_; std::vector init_at_commands_; - ModemModel model_; - std::unordered_map modem_model_map_ = {{"BG96", ModemModel::BG96}, - {"SIM800", ModemModel::SIM800}, - {"SIM7000", ModemModel::SIM7000}, - {"SIM7070", ModemModel::SIM7070}, - {"SIM7600", ModemModel::SIM7600}}; std::shared_ptr dte_{nullptr}; esp_netif_t *ppp_netif_{nullptr}; esp_modem_dte_config_t dte_config_; @@ -117,31 +106,6 @@ class ModemComponent : public Component { uint32_t command_delay_ = 500; // Will be true when power transitionning bool power_transition_ = false; - // time needed for power_pin to be low for poweron - std::unordered_map modem_model_ton_ = {{ModemModel::BG96, 600}, - {ModemModel::SIM800, 1300}, - {ModemModel::SIM7000, 1100}, - {ModemModel::SIM7070, 1100}, - {ModemModel::SIM7600, 500}}; - // time to wait after poweron for uart to be ready - std::unordered_map modem_model_tonuart_ = {{ModemModel::BG96, 4900}, - {ModemModel::SIM800, 3000}, - {ModemModel::SIM7000, 4500}, - {ModemModel::SIM7070, 2500}, - {ModemModel::SIM7600, 12000}}; - // time needed for power_pin to be high for poweroff - std::unordered_map modem_model_toff_ = {{ModemModel::BG96, 650}, - {ModemModel::SIM800, 200}, - {ModemModel::SIM7000, 1300}, - {ModemModel::SIM7070, 1300}, - {ModemModel::SIM7600, 2800}}; - // time to wait after for poweroff for uart to be really closed - std::unordered_map modem_model_toffuart_ = {{ModemModel::BG96, 2000}, - {ModemModel::SIM800, 3000}, - {ModemModel::SIM7000, 1800}, - {ModemModel::SIM7070, 1800}, - {ModemModel::SIM7600, 25000}}; - // separate handler for `on_not_responding` (we want to know when it's ended) Trigger<> *not_responding_cb_{nullptr}; CallbackManager on_state_callback_;