mirror of
https://github.com/esphome/esphome.git
synced 2024-12-25 15:04:54 +01:00
automation and better DCE recover
This commit is contained in:
parent
321594ae3d
commit
a6baff9085
4 changed files with 185 additions and 38 deletions
|
@ -6,12 +6,13 @@ from esphome.const import (
|
|||
CONF_USERNAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_MODEL,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
|
||||
from esphome.components.binary_sensor import BinarySensor
|
||||
from esphome import automation
|
||||
|
||||
CODEOWNERS = ["@oarcher"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
@ -19,18 +20,19 @@ AUTO_LOAD = ["network"]
|
|||
# following should be removed if conflicts are resolved (so we can have a wifi ap using modem)
|
||||
CONFLICTS_WITH = ["wifi", "captive_portal", "ethernet"]
|
||||
|
||||
CONF_POWER_PIN = "power_pin"
|
||||
CONF_FLIGHT_PIN = "flight_pin"
|
||||
CONF_PIN_CODE = "pin_code"
|
||||
CONF_APN = "apn"
|
||||
CONF_STATUS_PIN = "status_pin"
|
||||
CONF_DTR_PIN = "dtr_pin"
|
||||
CONF_INIT_AT = "init_at"
|
||||
CONF_READY = "ready"
|
||||
|
||||
# CONF_ON_SCRIPT = "on_script"
|
||||
# CONF_OFF_SCRIPT = "off_script"
|
||||
CONF_ON_NOT_RESPONDING = "on_not_responding"
|
||||
|
||||
modem_ns = cg.esphome_ns.namespace("modem")
|
||||
ModemComponent = modem_ns.class_("ModemComponent", cg.Component)
|
||||
ModemNotRespondingTrigger = modem_ns.class_(
|
||||
"ModemNotRespondingTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
|
@ -41,16 +43,21 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Required(CONF_RX_PIN): cv.positive_int,
|
||||
cv.Required(CONF_MODEL): cv.string,
|
||||
cv.Required(CONF_APN): cv.string,
|
||||
cv.Required(CONF_READY): cv.use_id(BinarySensor),
|
||||
cv.Optional(CONF_FLIGHT_PIN): cv.positive_int,
|
||||
cv.Optional(CONF_POWER_PIN): cv.positive_int,
|
||||
cv.Optional(CONF_STATUS_PIN): cv.positive_int,
|
||||
# cv.Optional(CONF_ON_SCRIPT): cv.use_id(Script),
|
||||
# cv.Optional(CONF_OFF_SCRIPT): cv.use_id(Script),
|
||||
cv.Optional(CONF_DTR_PIN): cv.positive_int,
|
||||
cv.Optional(CONF_PIN_CODE): cv.string_strict,
|
||||
cv.Optional(CONF_USERNAME): cv.string,
|
||||
cv.Optional(CONF_PASSWORD): cv.string,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string,
|
||||
cv.Optional(CONF_INIT_AT): cv.All(cv.ensure_list(cv.string)),
|
||||
cv.Optional(CONF_ON_NOT_RESPONDING): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
ModemNotRespondingTrigger
|
||||
)
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.require_framework_version(
|
||||
|
@ -102,9 +109,17 @@ async def to_code(config):
|
|||
for cmd in init_at:
|
||||
cg.add(var.add_init_at_command(cmd))
|
||||
|
||||
if modem_ready := config.get(CONF_READY, None):
|
||||
modem_ready_sensor = await cg.get_variable(modem_ready)
|
||||
cg.add(var.set_ready_bsensor(modem_ready_sensor))
|
||||
# if modem_ready := config.get(CONF_READY, None):
|
||||
# modem_ready_sensor = await cg.get_variable(modem_ready)
|
||||
# cg.add(var.set_ready_bsensor(modem_ready_sensor))
|
||||
|
||||
# if conf_on_script := config.get(CONF_ON_SCRIPT, None):
|
||||
# on_script = await cg.get_variable(conf_on_script)
|
||||
# cg.add(var.set_on_script(on_script))
|
||||
#
|
||||
# if conf_off_script := config.get(CONF_OFF_SCRIPT, None):
|
||||
# off_script = await cg.get_variable(conf_off_script)
|
||||
# cg.add(var.set_off_script(off_script))
|
||||
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
cg.add(var.set_apn(config[CONF_APN]))
|
||||
|
@ -114,4 +129,8 @@ async def to_code(config):
|
|||
cg.add(var.set_rx_pin(getattr(gpio_num_t, f"GPIO_NUM_{config[CONF_RX_PIN]}")))
|
||||
cg.add(var.set_tx_pin(getattr(gpio_num_t, f"GPIO_NUM_{config[CONF_TX_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)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
|
17
esphome/components/modem/automation.h
Normal file
17
esphome/components/modem/automation.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include "modem_component.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modem {
|
||||
|
||||
class ModemNotRespondingTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit ModemNotRespondingTrigger(ModemComponent *parent) {
|
||||
parent->add_on_not_responding_callback([this, parent]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace modem
|
||||
} // namespace esphome
|
|
@ -35,7 +35,20 @@ ModemComponent *global_modem_component; // NOLINT(cppcoreguidelines-avoid-non-c
|
|||
return; \
|
||||
}
|
||||
|
||||
using namespace esp_modem;
|
||||
std::string command_result_to_string(command_result err) {
|
||||
std::string res = "UNKNOWN";
|
||||
switch (err) {
|
||||
case command_result::FAIL:
|
||||
res = "FAIL";
|
||||
break;
|
||||
case command_result::OK:
|
||||
res = "OK";
|
||||
break;
|
||||
case command_result::TIMEOUT:
|
||||
res = "TIMEOUT";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ModemComponent::ModemComponent() { global_modem_component = this; }
|
||||
|
||||
|
@ -158,18 +171,8 @@ void ModemComponent::start_connect_() {
|
|||
|
||||
global_modem_component->got_ipv4_address_ = false;
|
||||
|
||||
this->dce->set_mode(modem_mode::COMMAND_MODE);
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
|
||||
command_result res = command_result::TIMEOUT;
|
||||
|
||||
res = this->dce->sync();
|
||||
|
||||
if (res != command_result::OK) {
|
||||
ESP_LOGW(TAG, "Unable to sync modem. Will retry later");
|
||||
return;
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -205,8 +208,7 @@ void ModemComponent::start_connect_() {
|
|||
if (this->dce->set_mode(modem_mode::CMUX_MODE)) {
|
||||
ESP_LOGD(TAG, "Modem has correctly entered multiplexed command/data mode");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to configure multiplexed command mode... exiting");
|
||||
return;
|
||||
ESP_LOGE(TAG, "Failed to configure multiplexed command mode. Trying to continue...");
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
|
||||
|
@ -215,7 +217,11 @@ void ModemComponent::start_connect_() {
|
|||
std::string result;
|
||||
command_result err = this->dce->at(cmd.c_str(), result, 1000);
|
||||
delay(100); // NOLINT
|
||||
ESP_LOGI(TAG, "Init AT command: %s (status %d) -> %s", cmd.c_str(), (int) err, result.c_str());
|
||||
if (err != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Error while executing '%s' command (status %s)", cmd.c_str(),
|
||||
command_result_to_string(err).c_str());
|
||||
}
|
||||
ESP_LOGI(TAG, "Init AT command: %s -> %s", cmd.c_str(), result.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,17 +235,62 @@ void ModemComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base
|
|||
|
||||
void ModemComponent::loop() {
|
||||
const uint32_t now = millis();
|
||||
static uint32_t last_log_time = now;
|
||||
static uint32_t last_health_check = now;
|
||||
const uint32_t healh_check_interval = 30000;
|
||||
|
||||
switch (this->state_) {
|
||||
case ModemComponentState::STOPPED:
|
||||
if ((this->on_script_ != nullptr && this->on_script_->is_running()) ||
|
||||
(this->off_script_ != nullptr && this->off_script_->is_running())) {
|
||||
break;
|
||||
}
|
||||
if (this->started_) {
|
||||
if (!this->modem_ready()) {
|
||||
if (now - last_log_time > 20000) {
|
||||
ESP_LOGD(TAG, "Waiting for the modem to be ready...");
|
||||
last_log_time = now;
|
||||
// ESP_LOGW(TAG, "Trying to recover dce");
|
||||
// ESP_ERROR_CHECK_WITHOUT_ABORT(this->dce->recover());
|
||||
// delay(1000);
|
||||
// ESP_LOGW(TAG, "Forcing undef mode");
|
||||
// ESP_ERROR_CHECK_WITHOUT_ABORT(this->dce->set_mode(esp_modem::modem_mode::UNDEF));
|
||||
// delay(1000);
|
||||
ESP_LOGW(TAG, "Forcing cmux manual mode mode");
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(this->dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE));
|
||||
delay(1000);
|
||||
// // ESP_LOGW(TAG, "Trying to recover dce");
|
||||
// // ESP_ERROR_CHECK_WITHOUT_ABORT(this->dce->recover());
|
||||
// // delay(1000);
|
||||
ESP_LOGW(TAG, "Forcing cmux manual command mode");
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(this->dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_COMMAND));
|
||||
delay(1000);
|
||||
ESP_LOGW(TAG, "Forcing cmux manual exit mode");
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(this->dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT));
|
||||
delay(1000);
|
||||
// ESP_LOGW(TAG, "Forcing command mode");
|
||||
// ESP_ERROR_CHECK_WITHOUT_ABORT(this->dce->set_mode(esp_modem::modem_mode::COMMAND_MODE));
|
||||
// delay(1000);
|
||||
// ESP_LOGW(TAG, "Forcing reset");
|
||||
// this->dce->reset();
|
||||
// ESP_LOGW(TAG, "Forcing hangup");
|
||||
// this->dce->hang_up();
|
||||
// this->send_at("AT+CGATT=0"); // disconnect network
|
||||
// delay(1000);
|
||||
// this->send_at("ATH"); // hangup
|
||||
// delay(1000);
|
||||
// delay(1000);
|
||||
// ESP_LOGW(TAG, "Forcing disconnect");
|
||||
// this->send_at("AT+CGATT=0"); // disconnect network
|
||||
// delay(1000);
|
||||
// ESP_LOGW(TAG, "Unable to sync modem");
|
||||
if (!this->modem_ready()) {
|
||||
this->on_not_responding_callback_.call();
|
||||
// if (this->on_script_ != nullptr) {
|
||||
// ESP_LOGD(TAG, "Executing recover_script");
|
||||
// this->on_script_->execute();
|
||||
// } else {
|
||||
// ESP_LOGE(TAG, "Modem not responding, and no recover_script");
|
||||
// }
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Modem is ready");
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Starting modem connection");
|
||||
this->state_ = ModemComponentState::CONNECTING;
|
||||
|
@ -260,7 +311,8 @@ void ModemComponent::loop() {
|
|||
this->status_clear_warning();
|
||||
} else if (now - this->connect_begin_ > 45000) {
|
||||
ESP_LOGW(TAG, "Connecting via Modem failed! Re-connecting...");
|
||||
this->start_connect_();
|
||||
this->state_ = ModemComponentState::STOPPED;
|
||||
// this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case ModemComponentState::CONNECTED:
|
||||
|
@ -271,6 +323,15 @@ void ModemComponent::loop() {
|
|||
ESP_LOGW(TAG, "Connection via Modem lost! Re-connecting...");
|
||||
this->state_ = ModemComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
} else {
|
||||
if ((now - last_health_check) >= healh_check_interval) {
|
||||
ESP_LOGV(TAG, "Health check");
|
||||
last_health_check = now;
|
||||
if (!this->send_at("AT+CGREG?")) {
|
||||
ESP_LOGW(TAG, "Modem not responding. Re-connecting...");
|
||||
this->state_ = ModemComponentState::STOPPED;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -293,6 +354,50 @@ void ModemComponent::dump_connect_params_() {
|
|||
ESP_LOGCONFIG(TAG, " DNS fallback: %s", network::IPAddress(dns_fallback_ip).str().c_str());
|
||||
}
|
||||
|
||||
bool ModemComponent::send_at(const std::string &cmd) {
|
||||
std::string result;
|
||||
// esp_modem::command_result err;
|
||||
bool status;
|
||||
ESP_LOGV(TAG, "Sending command: %s", cmd.c_str());
|
||||
status = this->dce->at(cmd, result, 3000) == esp_modem::command_result::OK;
|
||||
ESP_LOGV(TAG, "Result for command %s: %s (status %d)", cmd.c_str(), result.c_str(), status);
|
||||
return status;
|
||||
}
|
||||
|
||||
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;
|
||||
return this->get_imei(imei);
|
||||
}
|
||||
|
||||
void ModemComponent::add_on_not_responding_callback(std::function<void()> &&callback) {
|
||||
this->on_not_responding_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
} // namespace modem
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/template/binary_sensor/template_binary_sensor.h"
|
||||
#include "esphome/components/script/script.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
|
@ -58,15 +57,20 @@ class ModemComponent : public Component {
|
|||
void set_model(const std::string &model) {
|
||||
this->model_ = this->modem_model_map_.count(model) ? modem_model_map_[model] : ModemModel::UNKNOWN;
|
||||
}
|
||||
void set_ready_bsensor(binary_sensor::BinarySensor *modem_ready) { this->modem_ready_ = modem_ready; }
|
||||
void set_on_script(script::Script<> *on_script) { this->on_script_ = on_script; }
|
||||
void set_off_script(script::Script<> *off_script) { this->off_script_ = off_script; }
|
||||
void add_init_at_command(const std::string &cmd) { this->init_at_commands_.push_back(cmd); }
|
||||
bool modem_ready() { return this->modem_ready_->state; }
|
||||
bool send_at(const std::string &cmd);
|
||||
bool get_imei(std::string &result);
|
||||
bool modem_ready();
|
||||
void add_on_not_responding_callback(std::function<void()> &&callback);
|
||||
std::unique_ptr<DCE> dce;
|
||||
|
||||
protected:
|
||||
gpio_num_t rx_pin_ = gpio_num_t::GPIO_NUM_NC;
|
||||
gpio_num_t tx_pin_ = gpio_num_t::GPIO_NUM_NC;
|
||||
binary_sensor::BinarySensor *modem_ready_;
|
||||
script::Script<> *on_script_ = nullptr;
|
||||
script::Script<> *off_script_ = nullptr;
|
||||
std::string pin_code_;
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
|
@ -90,6 +94,8 @@ class ModemComponent : public Component {
|
|||
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
void dump_connect_params_();
|
||||
std::string use_address_;
|
||||
|
||||
CallbackManager<void()> on_not_responding_callback_;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
|
Loading…
Reference in a new issue