[Packet transport] Ping timeout sensor (#8694)

This commit is contained in:
DanielV 2025-07-03 03:25:46 +02:00 committed by GitHub
parent 658e4bac47
commit 798eef41b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 108 additions and 10 deletions

View File

@ -1,19 +1,76 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import binary_sensor from esphome.components import binary_sensor
from esphome.const import CONF_ID import esphome.config_validation as cv
from esphome.const import (
CONF_DATA,
CONF_ID,
CONF_NAME,
CONF_STATUS,
CONF_TYPE,
DEVICE_CLASS_CONNECTIVITY,
ENTITY_CATEGORY_DIAGNOSTIC,
)
import esphome.final_validate as fv
from . import ( from . import (
CONF_ENCRYPTION,
CONF_PING_PONG_ENABLE,
CONF_PROVIDER, CONF_PROVIDER,
CONF_PROVIDERS,
CONF_REMOTE_ID, CONF_REMOTE_ID,
CONF_TRANSPORT_ID, CONF_TRANSPORT_ID,
PacketTransport,
packet_transport_sensor_schema, packet_transport_sensor_schema,
provider_name_validate,
) )
CONFIG_SCHEMA = packet_transport_sensor_schema(binary_sensor.binary_sensor_schema()) STATUS_SENSOR_SCHEMA = binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(
{
cv.GenerateID(CONF_TRANSPORT_ID): cv.use_id(PacketTransport),
cv.Required(CONF_PROVIDER): provider_name_validate,
}
)
CONFIG_SCHEMA = cv.typed_schema(
{
CONF_DATA: packet_transport_sensor_schema(binary_sensor.binary_sensor_schema()),
CONF_STATUS: STATUS_SENSOR_SCHEMA,
},
key=CONF_TYPE,
default_type=CONF_DATA,
)
def _final_validate(config):
if config[CONF_TYPE] != CONF_STATUS:
# Only run this validation if a status sensor is being configured
return config
full_config = fv.full_config.get()
transport_path = full_config.get_path_for_id(config[CONF_TRANSPORT_ID])[:-1]
transport_config = full_config.get_config_for_path(transport_path)
if transport_config[CONF_PING_PONG_ENABLE] and any(
CONF_ENCRYPTION in p
for p in transport_config[CONF_PROVIDERS]
if p[CONF_NAME] == config[CONF_PROVIDER]
):
return config
raise cv.Invalid(
"Status sensor requires ping-pong to be enabled and the nominated provider to use encryption."
)
FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config): async def to_code(config):
var = await binary_sensor.new_binary_sensor(config) var = await binary_sensor.new_binary_sensor(config)
comp = await cg.get_variable(config[CONF_TRANSPORT_ID]) comp = await cg.get_variable(config[CONF_TRANSPORT_ID])
if config[CONF_TYPE] == CONF_STATUS:
cg.add(comp.set_provider_status_sensor(config[CONF_PROVIDER], var))
cg.add_define("USE_STATUS_SENSOR")
else: # CONF_DATA is default
remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID)) remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID))
cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var)) cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var))

View File

@ -317,8 +317,37 @@ void PacketTransport::update() {
auto now = millis() / 1000; auto now = millis() / 1000;
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
this->resend_ping_key_ = this->ping_pong_enable_; this->resend_ping_key_ = this->ping_pong_enable_;
ESP_LOGV(TAG, "Ping request, age %u", now - this->last_key_time_);
this->last_key_time_ = now; this->last_key_time_ = now;
} }
for (const auto &provider : this->providers_) {
uint32_t key_response_age = now - provider.second.last_key_response_time;
if (key_response_age > (this->ping_pong_recyle_time_ * 2u)) {
#ifdef USE_STATUS_SENSOR
if (provider.second.status_sensor != nullptr && provider.second.status_sensor->state) {
ESP_LOGI(TAG, "Ping status for %s timeout at %u with age %u", provider.first.c_str(), now, key_response_age);
provider.second.status_sensor->publish_state(false);
}
#endif
#ifdef USE_SENSOR
for (auto &sensor : this->remote_sensors_[provider.first]) {
sensor.second->publish_state(NAN);
}
#endif
#ifdef USE_BINARY_SENSOR
for (auto &sensor : this->remote_binary_sensors_[provider.first]) {
sensor.second->invalidate_state();
}
#endif
} else {
#ifdef USE_STATUS_SENSOR
if (provider.second.status_sensor != nullptr && !provider.second.status_sensor->state) {
ESP_LOGI(TAG, "Ping status for %s restored at %u with age %u", provider.first.c_str(), now, key_response_age);
provider.second.status_sensor->publish_state(true);
}
#endif
}
}
} }
void PacketTransport::add_key_(const char *name, uint32_t key) { void PacketTransport::add_key_(const char *name, uint32_t key) {
@ -437,7 +466,8 @@ void PacketTransport::process_(const std::vector<uint8_t> &data) {
if (decoder.decode(PING_KEY, key) == DECODE_OK) { if (decoder.decode(PING_KEY, key) == DECODE_OK) {
if (key == this->ping_key_) { if (key == this->ping_key_) {
ping_key_seen = true; ping_key_seen = true;
ESP_LOGV(TAG, "Found good ping key %X", (unsigned) key); provider.last_key_response_time = millis() / 1000;
ESP_LOGV(TAG, "Found good ping key %X at timestamp %" PRIu32, (unsigned) key, provider.last_key_response_time);
} else { } else {
ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key); ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key);
} }

View File

@ -8,7 +8,7 @@
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#endif #endif
#
#include <vector> #include <vector>
#include <map> #include <map>
@ -27,6 +27,10 @@ struct Provider {
std::vector<uint8_t> encryption_key; std::vector<uint8_t> encryption_key;
const char *name; const char *name;
uint32_t last_code[2]; uint32_t last_code[2];
uint32_t last_key_response_time;
#ifdef USE_STATUS_SENSOR
binary_sensor::BinarySensor *status_sensor{nullptr};
#endif
}; };
#ifdef USE_SENSOR #ifdef USE_SENSOR
@ -75,10 +79,7 @@ class PacketTransport : public PollingComponent {
void add_provider(const char *hostname) { void add_provider(const char *hostname) {
if (this->providers_.count(hostname) == 0) { if (this->providers_.count(hostname) == 0) {
Provider provider; Provider provider{};
provider.encryption_key = std::vector<uint8_t>{};
provider.last_code[0] = 0;
provider.last_code[1] = 0;
provider.name = hostname; provider.name = hostname;
this->providers_[hostname] = provider; this->providers_[hostname] = provider;
#ifdef USE_SENSOR #ifdef USE_SENSOR
@ -97,6 +98,11 @@ class PacketTransport : public PollingComponent {
void set_provider_encryption(const char *name, std::vector<uint8_t> key) { void set_provider_encryption(const char *name, std::vector<uint8_t> key) {
this->providers_[name].encryption_key = std::move(key); this->providers_[name].encryption_key = std::move(key);
} }
#ifdef USE_STATUS_SENSOR
void set_provider_status_sensor(const char *name, binary_sensor::BinarySensor *sensor) {
this->providers_[name].status_sensor = sensor;
}
#endif
void set_platform_name(const char *name) { this->platform_name_ = name; } void set_platform_name(const char *name) { this->platform_name_ = name; }
protected: protected:

View File

@ -86,6 +86,7 @@
#define USE_SELECT #define USE_SELECT
#define USE_SENSOR #define USE_SENSOR
#define USE_STATUS_LED #define USE_STATUS_LED
#define USE_STATUS_SENSOR
#define USE_SWITCH #define USE_SWITCH
#define USE_TEXT #define USE_TEXT
#define USE_TEXT_SENSOR #define USE_TEXT_SENSOR

View File

@ -36,5 +36,9 @@ binary_sensor:
- platform: packet_transport - platform: packet_transport
provider: unencrypted-device provider: unencrypted-device
id: other_binary_sensor_id id: other_binary_sensor_id
- platform: packet_transport
provider: some-device-name
type: status
name: Some-Device Status
- platform: template - platform: template
id: binary_sensor_id1 id: binary_sensor_id1