mirror of
https://github.com/esphome/esphome.git
synced 2026-09-22 12:38:40 +00:00
[openthread] Provide action to control poll_period when device MTD (#11766)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
pre-commit-ci-lite[bot]
Jonathan Swoboda
Claude Sonnet 4.6
parent
1d5490fd91
commit
6f36ce6429
@@ -1,3 +1,4 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32C5,
|
||||
@@ -226,11 +227,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
|
||||
cv.Optional(CONF_TLV): cv.All(cv.string_strict, _validate_tlv_hex),
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional(CONF_POLL_PERIOD): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_OUTPUT_POWER): cv.All(
|
||||
cv.decibel,
|
||||
_validate_txpower,
|
||||
),
|
||||
cv.Optional(CONF_POLL_PERIOD): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
).extend(_CONNECTION_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
||||
@@ -309,3 +310,37 @@ async def to_code(config):
|
||||
)
|
||||
zephyr_add_prj_conf(f"OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
|
||||
zephyr_add_prj_conf("MAIN_STACK_SIZE", 4096)
|
||||
|
||||
|
||||
# Actions
|
||||
OpenThreadComponentPollPeriodAction = openthread_ns.class_(
|
||||
"OpenThreadComponentPollPeriodAction",
|
||||
automation.Action,
|
||||
cg.Parented.template(OpenThreadComponent),
|
||||
)
|
||||
|
||||
POLL_PERIOD_ACTION_SCHEMA = automation.maybe_conf(
|
||||
CONF_POLL_PERIOD,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(OpenThreadComponent),
|
||||
cv.Required(CONF_POLL_PERIOD): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"openthread.set_poll_period",
|
||||
OpenThreadComponentPollPeriodAction,
|
||||
POLL_PERIOD_ACTION_SCHEMA,
|
||||
synchronous=True,
|
||||
)
|
||||
async def openthread_poll_period_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_POLL_PERIOD], args, cg.uint32)
|
||||
cg.add(var.set_poll_period(template_))
|
||||
return var
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_OPENTHREAD
|
||||
|
||||
#include "automation.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::openthread {
|
||||
|
||||
static const char *const TAG = "openthread.automation";
|
||||
|
||||
void OpenThreadComponentBaseAction::warn_ftd_no_op_() {
|
||||
ESP_LOGW(TAG, "OpenThread action has no effect on FTD devices (MTD only)");
|
||||
}
|
||||
|
||||
void OpenThreadComponentBaseAction::lock_and_apply_() {
|
||||
if (this->parent_->is_ready()) {
|
||||
if (auto lock = InstanceLock::try_acquire(LOCK_ACQUIRE_TIMEOUT_MS); lock) {
|
||||
if (auto *instance = lock.get_instance(); instance != nullptr) {
|
||||
this->apply_locked(instance);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to acquire lock in action");
|
||||
}
|
||||
} else {
|
||||
// Action may trigger early before setup, e.g. due to enabled "restore mode".
|
||||
// Trying to acquire lock would fail!
|
||||
//
|
||||
// But default component values already have been overwritten.
|
||||
// It is sufficient to let component apply those later during setup.
|
||||
ESP_LOGD(TAG, "Not (yet) ready to apply");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::openthread
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_OPENTHREAD
|
||||
#include "openthread.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::openthread {
|
||||
|
||||
/** Base class allowing to fetch OpenThread lock from parent component
|
||||
* while applying action
|
||||
*
|
||||
* - Nontemplate aspects belong here to avoid template bloat.
|
||||
* - Subclasses implement virtual action method that is called under lock.
|
||||
* - Seal leaf subclasses via @a final to support devirtualization.
|
||||
*/
|
||||
class OpenThreadComponentBaseAction : public Parented<OpenThreadComponent> {
|
||||
public:
|
||||
// Enforce ctor with parent argument (not without args)
|
||||
explicit OpenThreadComponentBaseAction(OpenThreadComponent *ot) : Parented<OpenThreadComponent>(ot) {}
|
||||
|
||||
protected:
|
||||
/** Handler to implement in subclass for applying action parts that need lock */
|
||||
virtual void apply_locked(otInstance *instance) = 0;
|
||||
|
||||
/** Fetch OT lock and then call @a apply_locked */
|
||||
void lock_and_apply_();
|
||||
|
||||
/** Log a warning that this action has no effect on FTD devices */
|
||||
void warn_ftd_no_op_();
|
||||
|
||||
/** Timeout (ms) for acquiring OT lock */
|
||||
static constexpr uint32_t LOCK_ACQUIRE_TIMEOUT_MS = 100;
|
||||
};
|
||||
|
||||
/** Action to set single poll period parameter */
|
||||
template<typename... Ts>
|
||||
class OpenThreadComponentPollPeriodAction final : public Action<Ts...>, public OpenThreadComponentBaseAction {
|
||||
TEMPLATABLE_VALUE(uint32_t, poll_period)
|
||||
|
||||
public:
|
||||
/* Passthrough ctor */
|
||||
using OpenThreadComponentBaseAction::OpenThreadComponentBaseAction;
|
||||
|
||||
protected:
|
||||
void play(const Ts &...x) override {
|
||||
#if CONFIG_OPENTHREAD_MTD
|
||||
this->parent_->set_poll_period(this->poll_period_.value(x...));
|
||||
|
||||
this->lock_and_apply_();
|
||||
#else
|
||||
this->warn_ftd_no_op_();
|
||||
#endif
|
||||
}
|
||||
|
||||
void apply_locked(otInstance *instance) override { this->parent_->apply_linkmode_(instance); }
|
||||
};
|
||||
|
||||
} // namespace esphome::openthread
|
||||
#endif
|
||||
@@ -266,5 +266,34 @@ void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
|
||||
ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
|
||||
}
|
||||
|
||||
void OpenThreadComponent::apply_linkmode_(otInstance *instance) {
|
||||
otLinkModeConfig link_mode_config{};
|
||||
#if CONFIG_OPENTHREAD_FTD
|
||||
link_mode_config.mRxOnWhenIdle = true;
|
||||
link_mode_config.mDeviceType = true;
|
||||
link_mode_config.mNetworkData = true;
|
||||
#elif CONFIG_OPENTHREAD_MTD
|
||||
if (this->poll_period_ > 0) {
|
||||
if (otLinkSetPollPeriod(instance, this->poll_period_) != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Failed to set pollperiod");
|
||||
}
|
||||
ESP_LOGD(TAG, "Link Polling Period: %" PRIu32, otLinkGetPollPeriod(instance));
|
||||
}
|
||||
link_mode_config.mRxOnWhenIdle = this->poll_period_ == 0;
|
||||
link_mode_config.mDeviceType = false;
|
||||
link_mode_config.mNetworkData = false;
|
||||
#endif
|
||||
|
||||
if (otThreadSetLinkMode(instance, link_mode_config) != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Failed to set linkmode");
|
||||
}
|
||||
#ifdef ESPHOME_LOG_HAS_DEBUG // Fetch link mode from OT only when DEBUG
|
||||
link_mode_config = otThreadGetLinkMode(instance);
|
||||
ESP_LOGD(TAG, "Link Mode Device Type: %s, Network Data: %s, RX On When Idle: %s",
|
||||
TRUEFALSE(link_mode_config.mDeviceType), TRUEFALSE(link_mode_config.mNetworkData),
|
||||
TRUEFALSE(link_mode_config.mRxOnWhenIdle));
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome::openthread
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace esphome::openthread {
|
||||
|
||||
class InstanceLock;
|
||||
|
||||
template<typename... Ts> class OpenThreadComponentPollPeriodAction;
|
||||
|
||||
class OpenThreadComponent final : public Component {
|
||||
public:
|
||||
OpenThreadComponent();
|
||||
@@ -41,12 +43,23 @@ class OpenThreadComponent final : public Component {
|
||||
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
|
||||
#if CONFIG_OPENTHREAD_MTD
|
||||
void set_poll_period(uint32_t poll_period) { this->poll_period_ = poll_period; }
|
||||
uint32_t get_poll_period() const { return this->poll_period_; }
|
||||
#endif
|
||||
void set_output_power(int8_t output_power) { this->output_power_ = output_power; }
|
||||
void set_connected(bool connected) { this->connected_ = connected; }
|
||||
static void on_state_changed(otChangedFlags flags, void *context);
|
||||
|
||||
protected:
|
||||
// Actions re-apply link mode under the OT lock; allow them to call apply_linkmode_()
|
||||
// without exposing this lock-sensitive, raw-instance method on the public API.
|
||||
template<typename... Ts> friend class OpenThreadComponentPollPeriodAction;
|
||||
|
||||
/** Apply Link Mode settings (incl poll period).
|
||||
* Callers running outside the OpenThread task must hold InstanceLock.
|
||||
* ot_main() runs on the OpenThread task itself and must not acquire the lock.
|
||||
*/
|
||||
void apply_linkmode_(otInstance *instance);
|
||||
|
||||
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
|
||||
otInstance *get_openthread_instance_();
|
||||
int openthread_stop_();
|
||||
|
||||
@@ -111,32 +111,7 @@ void OpenThreadComponent::ot_main() {
|
||||
|
||||
ESP_LOGD(TAG, "Thread Version: %" PRIu16, otThreadGetVersion());
|
||||
|
||||
otLinkModeConfig link_mode_config{};
|
||||
#if CONFIG_OPENTHREAD_FTD
|
||||
link_mode_config.mRxOnWhenIdle = true;
|
||||
link_mode_config.mDeviceType = true;
|
||||
link_mode_config.mNetworkData = true;
|
||||
#elif CONFIG_OPENTHREAD_MTD
|
||||
if (this->poll_period_ > 0) {
|
||||
if (otLinkSetPollPeriod(instance, this->poll_period_) != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Failed to set pollperiod");
|
||||
}
|
||||
ESP_LOGD(TAG, "Link Polling Period: %" PRIu32, otLinkGetPollPeriod(instance));
|
||||
}
|
||||
link_mode_config.mRxOnWhenIdle = this->poll_period_ == 0;
|
||||
link_mode_config.mDeviceType = false;
|
||||
link_mode_config.mNetworkData = false;
|
||||
#endif
|
||||
|
||||
if (otThreadSetLinkMode(instance, link_mode_config) != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Failed to set linkmode");
|
||||
}
|
||||
#ifdef ESPHOME_LOG_HAS_DEBUG // Fetch link mode from OT only when DEBUG
|
||||
link_mode_config = otThreadGetLinkMode(instance);
|
||||
ESP_LOGD(TAG, "Link Mode Device Type: %s, Network Data: %s, RX On When Idle: %s",
|
||||
TRUEFALSE(link_mode_config.mDeviceType), TRUEFALSE(link_mode_config.mNetworkData),
|
||||
TRUEFALSE(link_mode_config.mRxOnWhenIdle));
|
||||
#endif
|
||||
this->apply_linkmode_(instance);
|
||||
|
||||
if (this->output_power_.has_value()) {
|
||||
if (const auto err = otPlatRadioSetTransmitPower(instance, *this->output_power_); err != OT_ERROR_NONE) {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
network:
|
||||
enable_ipv6: true
|
||||
@@ -0,0 +1,20 @@
|
||||
<<: !include common.yaml
|
||||
|
||||
openthread:
|
||||
device_type: MTD
|
||||
force_dataset: false
|
||||
use_address: open-thread-test.local
|
||||
tlv: 0e080000000000010000000300001035060004001fffe00208e227ac6a7f24052f0708fdb753eb517cb4d3051062b2442a928d9ea3b947a1618fc4085a030f4f70656e5468726561642d393837330102987304105330d857354330133c05e1fd7ae81a910c0402a0f7f8
|
||||
poll_period: 5s
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Radio Always On"
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
turn_on_action:
|
||||
then:
|
||||
- openthread.set_poll_period: 0s
|
||||
turn_off_action:
|
||||
then:
|
||||
- openthread.set_poll_period: 5s
|
||||
@@ -1,14 +1,6 @@
|
||||
esp32:
|
||||
board: esp32-c6-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
log_level: DEBUG
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
<<: !include common.yaml
|
||||
|
||||
openthread:
|
||||
device_type: MTD
|
||||
channel: 13
|
||||
network_name: OpenThread-8f28
|
||||
network_key: 0xdfd34f0f05cad978ec4e32b0413038ff
|
||||
@@ -16,7 +8,4 @@ openthread:
|
||||
ext_pan_id: 0xd63e8e3e495ebbc3
|
||||
pskc: 0xc23a76e98f1a6483639b1ac1271e2e27
|
||||
mesh_local_prefix: fd53:145f:ed22:ad81::/64
|
||||
force_dataset: true
|
||||
use_address: open-thread-test.local
|
||||
poll_period: 20sec
|
||||
output_power: 1dBm
|
||||
|
||||
Reference in New Issue
Block a user