diff --git a/esphome/components/packet_transport/binary_sensor.py b/esphome/components/packet_transport/binary_sensor.py index 076e37e6bb..09bbf91c99 100644 --- a/esphome/components/packet_transport/binary_sensor.py +++ b/esphome/components/packet_transport/binary_sensor.py @@ -1,19 +1,76 @@ import esphome.codegen as cg 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 ( + CONF_ENCRYPTION, + CONF_PING_PONG_ENABLE, CONF_PROVIDER, + CONF_PROVIDERS, CONF_REMOTE_ID, CONF_TRANSPORT_ID, + PacketTransport, 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): var = await binary_sensor.new_binary_sensor(config) comp = await cg.get_variable(config[CONF_TRANSPORT_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)) + 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)) + cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var)) diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 5c721002b0..6684d43ff7 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -317,8 +317,37 @@ void PacketTransport::update() { auto now = millis() / 1000; if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { 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; } + 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) { @@ -437,7 +466,8 @@ void PacketTransport::process_(const std::vector &data) { if (decoder.decode(PING_KEY, key) == DECODE_OK) { if (key == this->ping_key_) { 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 { ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key); } diff --git a/esphome/components/packet_transport/packet_transport.h b/esphome/components/packet_transport/packet_transport.h index 34edb82963..a2370e9749 100644 --- a/esphome/components/packet_transport/packet_transport.h +++ b/esphome/components/packet_transport/packet_transport.h @@ -8,7 +8,7 @@ #ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" #endif -# + #include #include @@ -27,6 +27,10 @@ struct Provider { std::vector encryption_key; const char *name; 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 @@ -75,10 +79,7 @@ class PacketTransport : public PollingComponent { void add_provider(const char *hostname) { if (this->providers_.count(hostname) == 0) { - Provider provider; - provider.encryption_key = std::vector{}; - provider.last_code[0] = 0; - provider.last_code[1] = 0; + Provider provider{}; provider.name = hostname; this->providers_[hostname] = provider; #ifdef USE_SENSOR @@ -97,6 +98,11 @@ class PacketTransport : public PollingComponent { void set_provider_encryption(const char *name, std::vector 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; } protected: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index be872689f3..320b40dc90 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -86,6 +86,7 @@ #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED +#define USE_STATUS_SENSOR #define USE_SWITCH #define USE_TEXT #define USE_TEXT_SENSOR diff --git a/tests/components/packet_transport/common.yaml b/tests/components/packet_transport/common.yaml index cbb34c4572..9151cf27dc 100644 --- a/tests/components/packet_transport/common.yaml +++ b/tests/components/packet_transport/common.yaml @@ -36,5 +36,9 @@ binary_sensor: - platform: packet_transport provider: unencrypted-device id: other_binary_sensor_id + - platform: packet_transport + provider: some-device-name + type: status + name: Some-Device Status - platform: template id: binary_sensor_id1