diff --git a/esphome/components/modem/__init__.py b/esphome/components/modem/__init__.py index 058f2de597..360773eae5 100644 --- a/esphome/components/modem/__init__.py +++ b/esphome/components/modem/__init__.py @@ -26,6 +26,9 @@ CONFLICTS_WITH = ["wifi", "captive_portal", "ethernet"] CONF_PIN_CODE = "pin_code" CONF_APN = "apn" CONF_DTR_PIN = "dtr_pin" +CONF_STATUS_PIN = "status_pin" +CONF_POWER_PIN = "power_pin" +CONF_FLIGTH_PIN = "flight_pin" CONF_INIT_AT = "init_at" CONF_ON_NOT_RESPONDING = "on_not_responding" @@ -51,6 +54,8 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_RX_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_MODEL): cv.string, 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, cv.Optional(CONF_DTR_PIN): pins.internal_gpio_output_pin_schema, cv.Optional(CONF_PIN_CODE): cv.string_strict, cv.Optional(CONF_USERNAME): cv.string, @@ -140,6 +145,14 @@ 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 status_pin := config.get(CONF_STATUS_PIN, None): + pin = await cg.gpio_pin_expression(status_pin) + cg.add(var.set_status_pin(pin)) + + if power_pin := config.get(CONF_POWER_PIN, None): + pin = await cg.gpio_pin_expression(power_pin) + cg.add(var.set_power_pin(pin)) + for conf in config.get(CONF_ON_NOT_RESPONDING, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/modem/modem_component.cpp b/esphome/components/modem/modem_component.cpp index bd20f40c64..1e3f18ab2b 100644 --- a/esphome/components/modem/modem_component.cpp +++ b/esphome/components/modem/modem_component.cpp @@ -113,6 +113,31 @@ void ModemComponent::setup() { this->reset_(); + if (this->power_pin_) { + this->power_pin_->setup(); + + delay(100); // NOLINT + // this->power_pin_->digital_write(false); + } + + if (this->status_pin_) { + this->status_pin_->setup(); + delay(100); // NOLINT + if (this->get_power_status()) { + ESP_LOGI(TAG, "Modem is ON"); + this->poweroff(); + } else { + ESP_LOGI(TAG, "Modem is OFF"); + this->poweron(); + } + } + + if (this->modem_ready()) { + ESP_LOGD(TAG, "modem ready at setup"); + } else { + ESP_LOGD(TAG, "modem not ready at setup"); + } + ESP_LOGV(TAG, "Setup finished"); } @@ -168,6 +193,7 @@ void ModemComponent::reset_() { assert(this->dce); + // flow control not fully implemented, but kept here for future work if (this->dte_config_.uart_config.flow_control == ESP_MODEM_FLOW_CONTROL_HW) { if (command_result::OK != this->dce->set_flow_control(2, 2)) { ESP_LOGE(TAG, "Failed to set the set_flow_control mode"); @@ -180,6 +206,7 @@ void ModemComponent::reset_() { if (this->enabled_ && !this->modem_ready()) { ESP_LOGW(TAG, "Here..."); + // if the esp has rebooted, but the modem not, it is still in cmux mode // So we close cmux. // The drawback is that if the modem is poweroff, those commands will take some time to execute. @@ -424,6 +451,63 @@ void ModemComponent::disable() { } } +bool ModemComponent::get_power_status() { + if (!this->status_pin_) { + ESP_LOGV(TAG, "No status pin, assuming the modem is ON"); + return true; + } + 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 + bool final_status = init_status; + for (int i = 0; i < 5; i++) { + delay(10); + final_status = final_status && this->status_pin_->digital_read(); + } + if (final_status != init_status) { + ESP_LOGV(TAG, "Floating status pin detected for state %d", final_status); + } + ESP_LOGV(TAG, "power status: %d", final_status); + return final_status; +} + +void ModemComponent::poweron() { + if (this->power_pin_) { + Watchdog wdt(60); + this->power_pin_->digital_write(0); + delay(10); + this->power_pin_->digital_write(1); + delay(1010); + this->power_pin_->digital_write(0); + while (!this->get_power_status()) { + delay(this->command_delay_); + ESP_LOGV(TAG, "Waiting for modem to poweron"); + } + while (!this->modem_ready()) { + delay(500); // NOLINT + ESP_LOGV(TAG, "Waiting for modem to be ready after poweron"); + } + } +} + +void ModemComponent::poweroff() { + if (this->get_power_status()) { + if (this->power_pin_) { + ESP_LOGV(TAG, "Modem poweroff with power pin"); + Watchdog wdt(60); + this->power_pin_->digital_write(false); + delay(2600); // NOLINT + this->power_pin_->digital_write(true); + while (this->get_power_status()) { + delay(this->command_delay_); + ESP_LOGV(TAG, "Waiting for modem to poweroff"); + } + } else { + ESP_LOGD(TAG, "Modem poweroff with AT command (TODO)"); + } + } +} + void ModemComponent::dump_connect_params_() { esp_netif_ip_info_t ip; esp_netif_get_ip_info(this->ppp_netif_, &ip); diff --git a/esphome/components/modem/modem_component.h b/esphome/components/modem/modem_component.h index dc635982a0..bd826275fc 100644 --- a/esphome/components/modem/modem_component.h +++ b/esphome/components/modem/modem_component.h @@ -56,6 +56,8 @@ class ModemComponent : public Component { void set_use_address(const std::string &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_power_pin(InternalGPIOPin *power_pin) { this->power_pin_ = power_pin; } + void set_status_pin(InternalGPIOPin *status_pin) { this->status_pin_ = status_pin; } void set_username(const std::string &username) { this->username_ = username; } void set_password(const std::string &password) { this->password_ = password; } void set_pin_code(const std::string &pin_code) { this->pin_code_ = pin_code; } @@ -67,6 +69,9 @@ class ModemComponent : public Component { void add_init_at_command(const std::string &cmd) { this->init_at_commands_.push_back(cmd); } std::string send_at(const std::string &cmd); bool get_imei(std::string &result); + bool get_power_status(); + void poweron(); + void poweroff(); bool modem_ready(); void enable(); void disable(); @@ -77,6 +82,8 @@ class ModemComponent : public Component { void reset_(); // (re)create dte and dce InternalGPIOPin *tx_pin_; InternalGPIOPin *rx_pin_; + InternalGPIOPin *status_pin_{nullptr}; + InternalGPIOPin *power_pin_{nullptr}; std::string pin_code_; std::string username_; std::string password_;