mirror of
https://github.com/esphome/esphome.git
synced 2025-07-28 14:16:40 +00:00
[packet_transport] Extract packet encoding functionality (#8187)
This commit is contained in:
parent
84ebbf0762
commit
524cd4b4e3
@ -319,6 +319,7 @@ esphome/components/online_image/* @clydebarrow @guillempages
|
||||
esphome/components/opentherm/* @olegtarasov
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/packet_transport/* @clydebarrow
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @clydebarrow @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
|
201
esphome/components/packet_transport/__init__.py
Normal file
201
esphome/components/packet_transport/__init__.py
Normal file
@ -0,0 +1,201 @@
|
||||
"""ESPHome packet transport component."""
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.api import CONF_ENCRYPTION
|
||||
from esphome.components.binary_sensor import BinarySensor
|
||||
from esphome.components.sensor import Sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BINARY_SENSORS,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_KEY,
|
||||
CONF_NAME,
|
||||
CONF_PLATFORM,
|
||||
CONF_SENSORS,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
AUTO_LOAD = ["xxtea"]
|
||||
|
||||
packet_transport_ns = cg.esphome_ns.namespace("packet_transport")
|
||||
PacketTransport = packet_transport_ns.class_("PacketTransport", cg.PollingComponent)
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
DOMAIN = "packet_transport"
|
||||
CONF_BROADCAST = "broadcast"
|
||||
CONF_BROADCAST_ID = "broadcast_id"
|
||||
CONF_PROVIDER = "provider"
|
||||
CONF_PROVIDERS = "providers"
|
||||
CONF_REMOTE_ID = "remote_id"
|
||||
CONF_PING_PONG_ENABLE = "ping_pong_enable"
|
||||
CONF_PING_PONG_RECYCLE_TIME = "ping_pong_recycle_time"
|
||||
CONF_ROLLING_CODE_ENABLE = "rolling_code_enable"
|
||||
CONF_TRANSPORT_ID = "transport_id"
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def sensor_validation(cls: MockObjClass):
|
||||
return cv.maybe_simple_value(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(cls),
|
||||
cv.Optional(CONF_BROADCAST_ID): cv.validate_id_name,
|
||||
}
|
||||
),
|
||||
key=CONF_ID,
|
||||
)
|
||||
|
||||
|
||||
def provider_name_validate(value):
|
||||
value = cv.valid_name(value)
|
||||
if "_" in value:
|
||||
_LOGGER.warning(
|
||||
"Device names typically do not contain underscores - did you mean to use a hyphen in '%s'?",
|
||||
value,
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
ENCRYPTION_SCHEMA = {
|
||||
cv.Optional(CONF_ENCRYPTION): cv.maybe_simple_value(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_KEY): cv.string,
|
||||
}
|
||||
),
|
||||
key=CONF_KEY,
|
||||
)
|
||||
}
|
||||
|
||||
PROVIDER_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): provider_name_validate,
|
||||
}
|
||||
).extend(ENCRYPTION_SCHEMA)
|
||||
|
||||
|
||||
def validate_(config):
|
||||
if CONF_ENCRYPTION in config:
|
||||
if CONF_SENSORS not in config and CONF_BINARY_SENSORS not in config:
|
||||
raise cv.Invalid("No sensors or binary sensors to encrypt")
|
||||
elif config[CONF_ROLLING_CODE_ENABLE]:
|
||||
raise cv.Invalid("Rolling code requires an encryption key")
|
||||
if config[CONF_PING_PONG_ENABLE]:
|
||||
if not any(CONF_ENCRYPTION in p for p in config.get(CONF_PROVIDERS) or ()):
|
||||
raise cv.Invalid("Ping-pong requires at least one encrypted provider")
|
||||
return config
|
||||
|
||||
|
||||
TRANSPORT_SCHEMA = (
|
||||
cv.polling_component_schema("15s")
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_ROLLING_CODE_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PING_PONG_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_PING_PONG_RECYCLE_TIME, default="600s"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_SENSORS): cv.ensure_list(sensor_validation(Sensor)),
|
||||
cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(
|
||||
sensor_validation(BinarySensor)
|
||||
),
|
||||
cv.Optional(CONF_PROVIDERS, default=[]): cv.ensure_list(PROVIDER_SCHEMA),
|
||||
},
|
||||
)
|
||||
.extend(ENCRYPTION_SCHEMA)
|
||||
.add_extra(validate_)
|
||||
)
|
||||
|
||||
|
||||
def transport_schema(cls):
|
||||
return TRANSPORT_SCHEMA.extend({cv.GenerateID(): cv.declare_id(cls)})
|
||||
|
||||
|
||||
# Build a list of sensors for this platform
|
||||
CORE.data[DOMAIN] = {CONF_SENSORS: []}
|
||||
|
||||
|
||||
def get_sensors(transport_id):
|
||||
"""Return the list of sensors for this platform."""
|
||||
return (
|
||||
sensor
|
||||
for sensor in CORE.data[DOMAIN][CONF_SENSORS]
|
||||
if sensor[CONF_TRANSPORT_ID] == transport_id
|
||||
)
|
||||
|
||||
|
||||
def validate_packet_transport_sensor(config):
|
||||
if CONF_NAME in config and CONF_INTERNAL not in config:
|
||||
raise cv.Invalid("Must provide internal: config when using name:")
|
||||
CORE.data[DOMAIN][CONF_SENSORS].append(config)
|
||||
return config
|
||||
|
||||
|
||||
def packet_transport_sensor_schema(base_schema):
|
||||
return cv.All(
|
||||
base_schema.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_TRANSPORT_ID): cv.use_id(PacketTransport),
|
||||
cv.Optional(CONF_REMOTE_ID): cv.string_strict,
|
||||
cv.Required(CONF_PROVIDER): provider_name_validate,
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_ID, CONF_REMOTE_ID),
|
||||
validate_packet_transport_sensor,
|
||||
)
|
||||
|
||||
|
||||
def hash_encryption_key(config: dict):
|
||||
return list(hashlib.sha256(config[CONF_KEY].encode()).digest())
|
||||
|
||||
|
||||
async def register_packet_transport(var, config):
|
||||
var = await cg.register_component(var, config)
|
||||
cg.add(var.set_rolling_code_enable(config[CONF_ROLLING_CODE_ENABLE]))
|
||||
cg.add(var.set_ping_pong_enable(config[CONF_PING_PONG_ENABLE]))
|
||||
cg.add(
|
||||
var.set_ping_pong_recycle_time(
|
||||
config[CONF_PING_PONG_RECYCLE_TIME].total_seconds
|
||||
)
|
||||
)
|
||||
# Get directly configured providers, plus those from sensors and binary sensors
|
||||
providers = {
|
||||
sensor[CONF_PROVIDER] for sensor in get_sensors(config[CONF_ID])
|
||||
}.union(x[CONF_NAME] for x in config[CONF_PROVIDERS])
|
||||
for provider in providers:
|
||||
cg.add(var.add_provider(provider))
|
||||
for provider in config[CONF_PROVIDERS]:
|
||||
name = provider[CONF_NAME]
|
||||
if encryption := provider.get(CONF_ENCRYPTION):
|
||||
cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption)))
|
||||
|
||||
for sens_conf in config.get(CONF_SENSORS, ()):
|
||||
sens_id = sens_conf[CONF_ID]
|
||||
sensor = await cg.get_variable(sens_id)
|
||||
bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id)
|
||||
cg.add(var.add_sensor(bcst_id, sensor))
|
||||
for sens_conf in config.get(CONF_BINARY_SENSORS, ()):
|
||||
sens_id = sens_conf[CONF_ID]
|
||||
sensor = await cg.get_variable(sens_id)
|
||||
bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id)
|
||||
cg.add(var.add_binary_sensor(bcst_id, sensor))
|
||||
|
||||
if encryption := config.get(CONF_ENCRYPTION):
|
||||
cg.add(var.set_encryption_key(hash_encryption_key(encryption)))
|
||||
return providers
|
||||
|
||||
|
||||
async def new_packet_transport(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_platform_name(config[CONF_PLATFORM]))
|
||||
providers = await register_packet_transport(var, config)
|
||||
return var, providers
|
19
esphome/components/packet_transport/binary_sensor.py
Normal file
19
esphome/components/packet_transport/binary_sensor.py
Normal file
@ -0,0 +1,19 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from . import (
|
||||
CONF_PROVIDER,
|
||||
CONF_REMOTE_ID,
|
||||
CONF_TRANSPORT_ID,
|
||||
packet_transport_sensor_schema,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = packet_transport_sensor_schema(binary_sensor.binary_sensor_schema())
|
||||
|
||||
|
||||
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))
|
534
esphome/components/packet_transport/packet_transport.cpp
Normal file
534
esphome/components/packet_transport/packet_transport.cpp
Normal file
@ -0,0 +1,534 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "packet_transport.h"
|
||||
|
||||
#include "esphome/components/xxtea/xxtea.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace packet_transport {
|
||||
/**
|
||||
* Structure of a data packet; everything is little-endian
|
||||
*
|
||||
* --- In clear text ---
|
||||
* MAGIC_NUMBER: 16 bits
|
||||
* host name length: 1 byte
|
||||
* host name: (length) bytes
|
||||
* padding: 0 or more null bytes to a 4 byte boundary
|
||||
*
|
||||
* --- Encrypted (if key set) ----
|
||||
* DATA_KEY: 1 byte: OR ROLLING_CODE_KEY:
|
||||
* Rolling code (if enabled): 8 bytes
|
||||
* Ping keys: if any
|
||||
* repeat:
|
||||
* PING_KEY: 1 byte
|
||||
* ping code: 4 bytes
|
||||
* Sensors:
|
||||
* repeat:
|
||||
* SENSOR_KEY: 1 byte
|
||||
* float value: 4 bytes
|
||||
* name length: 1 byte
|
||||
* name
|
||||
* Binary Sensors:
|
||||
* repeat:
|
||||
* BINARY_SENSOR_KEY: 1 byte
|
||||
* bool value: 1 bytes
|
||||
* name length: 1 byte
|
||||
* name
|
||||
*
|
||||
* Padded to a 4 byte boundary with nulls
|
||||
*
|
||||
* Structure of a ping request packet:
|
||||
* --- In clear text ---
|
||||
* MAGIC_PING: 16 bits
|
||||
* host name length: 1 byte
|
||||
* host name: (length) bytes
|
||||
* Ping key (4 bytes)
|
||||
*
|
||||
*/
|
||||
static const char *const TAG = "packet_transport";
|
||||
|
||||
static size_t round4(size_t value) { return (value + 3) & ~3; }
|
||||
|
||||
union FuData {
|
||||
uint32_t u32;
|
||||
float f32;
|
||||
};
|
||||
|
||||
static const uint16_t MAGIC_NUMBER = 0x4553;
|
||||
static const uint16_t MAGIC_PING = 0x5048;
|
||||
static const uint32_t PREF_HASH = 0x45535043;
|
||||
enum DataKey {
|
||||
ZERO_FILL_KEY,
|
||||
DATA_KEY,
|
||||
SENSOR_KEY,
|
||||
BINARY_SENSOR_KEY,
|
||||
PING_KEY,
|
||||
ROLLING_CODE_KEY,
|
||||
};
|
||||
|
||||
enum DecodeResult {
|
||||
DECODE_OK,
|
||||
DECODE_UNMATCHED,
|
||||
DECODE_ERROR,
|
||||
DECODE_EMPTY,
|
||||
};
|
||||
|
||||
static const size_t MAX_PING_KEYS = 4;
|
||||
|
||||
static inline void add(std::vector<uint8_t> &vec, uint32_t data) {
|
||||
vec.push_back(data & 0xFF);
|
||||
vec.push_back((data >> 8) & 0xFF);
|
||||
vec.push_back((data >> 16) & 0xFF);
|
||||
vec.push_back((data >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
class PacketDecoder {
|
||||
public:
|
||||
PacketDecoder(const uint8_t *buffer, size_t len) : buffer_(buffer), len_(len) {}
|
||||
|
||||
DecodeResult decode_string(char *data, size_t maxlen) {
|
||||
if (this->position_ == this->len_)
|
||||
return DECODE_EMPTY;
|
||||
auto len = this->buffer_[this->position_];
|
||||
if (len == 0 || this->position_ + 1 + len > this->len_ || len >= maxlen)
|
||||
return DECODE_ERROR;
|
||||
this->position_++;
|
||||
memcpy(data, this->buffer_ + this->position_, len);
|
||||
data[len] = 0;
|
||||
this->position_ += len;
|
||||
return DECODE_OK;
|
||||
}
|
||||
|
||||
template<typename T> DecodeResult get(T &data) {
|
||||
if (this->position_ + sizeof(T) > this->len_)
|
||||
return DECODE_ERROR;
|
||||
T value = 0;
|
||||
for (size_t i = 0; i != sizeof(T); ++i) {
|
||||
value += this->buffer_[this->position_++] << (i * 8);
|
||||
}
|
||||
data = value;
|
||||
return DECODE_OK;
|
||||
}
|
||||
|
||||
template<typename T> DecodeResult decode(uint8_t key, T &data) {
|
||||
if (this->position_ == this->len_)
|
||||
return DECODE_EMPTY;
|
||||
if (this->buffer_[this->position_] != key)
|
||||
return DECODE_UNMATCHED;
|
||||
if (this->position_ + 1 + sizeof(T) > this->len_)
|
||||
return DECODE_ERROR;
|
||||
this->position_++;
|
||||
T value = 0;
|
||||
for (size_t i = 0; i != sizeof(T); ++i) {
|
||||
value += this->buffer_[this->position_++] << (i * 8);
|
||||
}
|
||||
data = value;
|
||||
return DECODE_OK;
|
||||
}
|
||||
|
||||
template<typename T> DecodeResult decode(uint8_t key, char *buf, size_t buflen, T &data) {
|
||||
if (this->position_ == this->len_)
|
||||
return DECODE_EMPTY;
|
||||
if (this->buffer_[this->position_] != key)
|
||||
return DECODE_UNMATCHED;
|
||||
this->position_++;
|
||||
T value = 0;
|
||||
for (size_t i = 0; i != sizeof(T); ++i) {
|
||||
value += this->buffer_[this->position_++] << (i * 8);
|
||||
}
|
||||
data = value;
|
||||
return this->decode_string(buf, buflen);
|
||||
}
|
||||
|
||||
DecodeResult decode(uint8_t key) {
|
||||
if (this->position_ == this->len_)
|
||||
return DECODE_EMPTY;
|
||||
if (this->buffer_[this->position_] != key)
|
||||
return DECODE_UNMATCHED;
|
||||
this->position_++;
|
||||
return DECODE_OK;
|
||||
}
|
||||
|
||||
size_t get_remaining_size() const { return this->len_ - this->position_; }
|
||||
|
||||
// align the pointer to the given byte boundary
|
||||
bool bump_to(size_t boundary) {
|
||||
auto newpos = this->position_;
|
||||
auto offset = this->position_ % boundary;
|
||||
if (offset != 0) {
|
||||
newpos += boundary - offset;
|
||||
}
|
||||
if (newpos >= this->len_)
|
||||
return false;
|
||||
this->position_ = newpos;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decrypt(const uint32_t *key) {
|
||||
if (this->get_remaining_size() % 4 != 0) {
|
||||
return false;
|
||||
}
|
||||
xxtea::decrypt((uint32_t *) (this->buffer_ + this->position_), this->get_remaining_size() / 4, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint8_t *buffer_;
|
||||
size_t len_;
|
||||
size_t position_{};
|
||||
};
|
||||
|
||||
static inline void add(std::vector<uint8_t> &vec, uint8_t data) { vec.push_back(data); }
|
||||
static inline void add(std::vector<uint8_t> &vec, uint16_t data) {
|
||||
vec.push_back((uint8_t) data);
|
||||
vec.push_back((uint8_t) (data >> 8));
|
||||
}
|
||||
static inline void add(std::vector<uint8_t> &vec, DataKey data) { vec.push_back(data); }
|
||||
static void add(std::vector<uint8_t> &vec, const char *str) {
|
||||
auto len = strlen(str);
|
||||
vec.push_back(len);
|
||||
for (size_t i = 0; i != len; i++) {
|
||||
vec.push_back(*str++);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketTransport::setup() {
|
||||
this->name_ = App.get_name().c_str();
|
||||
if (strlen(this->name_) > 255) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Device name exceeds 255 chars");
|
||||
return;
|
||||
}
|
||||
this->resend_ping_key_ = this->ping_pong_enable_;
|
||||
this->pref_ = global_preferences->make_preference<uint32_t>(PREF_HASH, true);
|
||||
if (this->rolling_code_enable_) {
|
||||
// restore the upper 32 bits of the rolling code, increment and save.
|
||||
this->pref_.load(&this->rolling_code_[1]);
|
||||
this->rolling_code_[1]++;
|
||||
this->pref_.save(&this->rolling_code_[1]);
|
||||
// must make sure it's saved immediately
|
||||
global_preferences->sync();
|
||||
this->ping_key_ = random_uint32();
|
||||
ESP_LOGV(TAG, "Rolling code incremented, upper part now %u", (unsigned) this->rolling_code_[1]);
|
||||
}
|
||||
#ifdef USE_SENSOR
|
||||
for (auto &sensor : this->sensors_) {
|
||||
sensor.sensor->add_on_state_callback([this, &sensor](float x) {
|
||||
this->updated_ = true;
|
||||
sensor.updated = true;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto &sensor : this->binary_sensors_) {
|
||||
sensor.sensor->add_on_state_callback([this, &sensor](bool value) {
|
||||
this->updated_ = true;
|
||||
sensor.updated = true;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
// initialise the header. This is invariant.
|
||||
add(this->header_, MAGIC_NUMBER);
|
||||
add(this->header_, this->name_);
|
||||
// pad to a multiple of 4 bytes
|
||||
while (this->header_.size() & 0x3)
|
||||
this->header_.push_back(0);
|
||||
}
|
||||
|
||||
void PacketTransport::init_data_() {
|
||||
this->data_.clear();
|
||||
if (this->rolling_code_enable_) {
|
||||
add(this->data_, ROLLING_CODE_KEY);
|
||||
add(this->data_, this->rolling_code_[0]);
|
||||
add(this->data_, this->rolling_code_[1]);
|
||||
this->increment_code_();
|
||||
} else {
|
||||
add(this->data_, DATA_KEY);
|
||||
}
|
||||
for (auto pkey : this->ping_keys_) {
|
||||
add(this->data_, PING_KEY);
|
||||
add(this->data_, pkey.second);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketTransport::flush_() {
|
||||
if (!this->should_send() || this->data_.empty())
|
||||
return;
|
||||
auto header_len = round4(this->header_.size());
|
||||
auto len = round4(data_.size());
|
||||
auto encode_buffer = std::vector<uint8_t>(round4(header_len + len));
|
||||
memcpy(encode_buffer.data(), this->header_.data(), this->header_.size());
|
||||
memcpy(encode_buffer.data() + header_len, this->data_.data(), this->data_.size());
|
||||
if (this->is_encrypted_()) {
|
||||
xxtea::encrypt((uint32_t *) (encode_buffer.data() + header_len), len / 4,
|
||||
(uint32_t *) this->encryption_key_.data());
|
||||
}
|
||||
this->send_packet(encode_buffer);
|
||||
}
|
||||
|
||||
void PacketTransport::add_binary_data_(uint8_t key, const char *id, bool data) {
|
||||
auto len = 1 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > this->get_max_packet_size()) {
|
||||
this->flush_();
|
||||
}
|
||||
add(this->data_, key);
|
||||
add(this->data_, (uint8_t) data);
|
||||
add(this->data_, id);
|
||||
}
|
||||
void PacketTransport::add_data_(uint8_t key, const char *id, float data) {
|
||||
FuData udata{.f32 = data};
|
||||
this->add_data_(key, id, udata.u32);
|
||||
}
|
||||
|
||||
void PacketTransport::add_data_(uint8_t key, const char *id, uint32_t data) {
|
||||
auto len = 4 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > this->get_max_packet_size()) {
|
||||
this->flush_();
|
||||
}
|
||||
add(this->data_, key);
|
||||
add(this->data_, data);
|
||||
add(this->data_, id);
|
||||
}
|
||||
void PacketTransport::send_data_(bool all) {
|
||||
if (!this->should_send())
|
||||
return;
|
||||
this->init_data_();
|
||||
#ifdef USE_SENSOR
|
||||
for (auto &sensor : this->sensors_) {
|
||||
if (all || sensor.updated) {
|
||||
sensor.updated = false;
|
||||
this->add_data_(SENSOR_KEY, sensor.id, sensor.sensor->get_state());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto &sensor : this->binary_sensors_) {
|
||||
if (all || sensor.updated) {
|
||||
sensor.updated = false;
|
||||
this->add_binary_data_(BINARY_SENSOR_KEY, sensor.id, sensor.sensor->state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
this->flush_();
|
||||
this->updated_ = false;
|
||||
}
|
||||
|
||||
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_;
|
||||
this->last_key_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketTransport::add_key_(const char *name, uint32_t key) {
|
||||
if (!this->is_encrypted_())
|
||||
return;
|
||||
if (this->ping_keys_.count(name) == 0 && this->ping_keys_.size() == MAX_PING_KEYS) {
|
||||
ESP_LOGW(TAG, "Ping key from %s discarded", name);
|
||||
return;
|
||||
}
|
||||
this->ping_keys_[name] = key;
|
||||
this->updated_ = true;
|
||||
ESP_LOGV(TAG, "Ping key from %s now %X", name, (unsigned) key);
|
||||
}
|
||||
|
||||
static bool process_rolling_code(Provider &provider, PacketDecoder &decoder) {
|
||||
uint32_t code0, code1;
|
||||
if (decoder.get(code0) != DECODE_OK || decoder.get(code1) != DECODE_OK) {
|
||||
ESP_LOGW(TAG, "Rolling code requires 8 bytes");
|
||||
return false;
|
||||
}
|
||||
if (code1 < provider.last_code[1] || (code1 == provider.last_code[1] && code0 <= provider.last_code[0])) {
|
||||
ESP_LOGW(TAG, "Rolling code for %s %08lX:%08lX is old", provider.name, (unsigned long) code1,
|
||||
(unsigned long) code0);
|
||||
return false;
|
||||
}
|
||||
provider.last_code[0] = code0;
|
||||
provider.last_code[1] = code1;
|
||||
ESP_LOGV(TAG, "Saw new rolling code for %s %08lX:%08lX", provider.name, (unsigned long) code1, (unsigned long) code0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a received packet
|
||||
*/
|
||||
void PacketTransport::process_(std::vector<uint8_t> &data) {
|
||||
auto ping_key_seen = !this->ping_pong_enable_;
|
||||
PacketDecoder decoder((data.data()), data.size());
|
||||
char namebuf[256]{};
|
||||
uint8_t byte;
|
||||
FuData rdata{};
|
||||
uint16_t magic;
|
||||
if (decoder.get(magic) != DECODE_OK) {
|
||||
ESP_LOGD(TAG, "Short buffer");
|
||||
return;
|
||||
}
|
||||
if (magic != MAGIC_NUMBER && magic != MAGIC_PING) {
|
||||
ESP_LOGV(TAG, "Bad magic %X", magic);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decoder.decode_string(namebuf, sizeof namebuf) != DECODE_OK) {
|
||||
ESP_LOGV(TAG, "Bad hostname length");
|
||||
return;
|
||||
}
|
||||
if (strcmp(this->name_, namebuf) == 0) {
|
||||
ESP_LOGVV(TAG, "Ignoring our own data");
|
||||
return;
|
||||
}
|
||||
if (magic == MAGIC_PING) {
|
||||
uint32_t key;
|
||||
if (decoder.get(key) != DECODE_OK) {
|
||||
ESP_LOGW(TAG, "Bad ping request");
|
||||
return;
|
||||
}
|
||||
this->add_key_(namebuf, key);
|
||||
ESP_LOGV(TAG, "Updated ping key for %s to %08X", namebuf, (unsigned) key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->providers_.count(namebuf) == 0) {
|
||||
ESP_LOGVV(TAG, "Unknown hostname %s", namebuf);
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Found hostname %s", namebuf);
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
auto &sensors = this->remote_sensors_[namebuf];
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
auto &binary_sensors = this->remote_binary_sensors_[namebuf];
|
||||
#endif
|
||||
|
||||
if (!decoder.bump_to(4)) {
|
||||
ESP_LOGW(TAG, "Bad packet length %zu", data.size());
|
||||
}
|
||||
auto len = decoder.get_remaining_size();
|
||||
if (round4(len) != len) {
|
||||
ESP_LOGW(TAG, "Bad payload length %zu", len);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &provider = this->providers_[namebuf];
|
||||
// if encryption not used with this host, ping check is pointless since it would be easily spoofed.
|
||||
if (provider.encryption_key.empty())
|
||||
ping_key_seen = true;
|
||||
|
||||
if (!provider.encryption_key.empty()) {
|
||||
decoder.decrypt((const uint32_t *) provider.encryption_key.data());
|
||||
}
|
||||
if (decoder.get(byte) != DECODE_OK) {
|
||||
ESP_LOGV(TAG, "No key byte");
|
||||
return;
|
||||
}
|
||||
|
||||
if (byte == ROLLING_CODE_KEY) {
|
||||
if (!process_rolling_code(provider, decoder))
|
||||
return;
|
||||
} else if (byte != DATA_KEY) {
|
||||
ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte);
|
||||
return;
|
||||
}
|
||||
uint32_t key;
|
||||
while (decoder.get_remaining_size() != 0) {
|
||||
if (decoder.decode(ZERO_FILL_KEY) == DECODE_OK)
|
||||
continue;
|
||||
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);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!ping_key_seen) {
|
||||
ESP_LOGW(TAG, "Ping key not seen");
|
||||
this->resend_ping_key_ = true;
|
||||
break;
|
||||
}
|
||||
if (decoder.decode(BINARY_SENSOR_KEY, namebuf, sizeof(namebuf), byte) == DECODE_OK) {
|
||||
ESP_LOGV(TAG, "Got binary sensor %s %d", namebuf, byte);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (binary_sensors.count(namebuf) != 0)
|
||||
binary_sensors[namebuf]->publish_state(byte != 0);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
if (decoder.decode(SENSOR_KEY, namebuf, sizeof(namebuf), rdata.u32) == DECODE_OK) {
|
||||
ESP_LOGV(TAG, "Got sensor %s %f", namebuf, rdata.f32);
|
||||
#ifdef USE_SENSOR
|
||||
if (sensors.count(namebuf) != 0)
|
||||
sensors[namebuf]->publish_state(rdata.f32);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
if (decoder.get(byte) == DECODE_OK) {
|
||||
ESP_LOGW(TAG, "Unknown key %X", byte);
|
||||
ESP_LOGD(TAG, "Buffer pos: %zu contents: %s", data.size() - decoder.get_remaining_size(),
|
||||
format_hex_pretty(data).c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketTransport::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Packet Transport:");
|
||||
ESP_LOGCONFIG(TAG, " Platform: %s", this->platform_name_);
|
||||
ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(this->is_encrypted_()));
|
||||
ESP_LOGCONFIG(TAG, " Ping-pong: %s", YESNO(this->ping_pong_enable_));
|
||||
#ifdef USE_SENSOR
|
||||
for (auto sensor : this->sensors_)
|
||||
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto sensor : this->binary_sensors_)
|
||||
ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.id);
|
||||
#endif
|
||||
for (const auto &host : this->providers_) {
|
||||
ESP_LOGCONFIG(TAG, " Remote host: %s", host.first.c_str());
|
||||
ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(!host.second.encryption_key.empty()));
|
||||
#ifdef USE_SENSOR
|
||||
for (const auto &sensor : this->remote_sensors_[host.first.c_str()])
|
||||
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.first.c_str());
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (const auto &sensor : this->remote_binary_sensors_[host.first.c_str()])
|
||||
ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.first.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void PacketTransport::increment_code_() {
|
||||
if (this->rolling_code_enable_) {
|
||||
if (++this->rolling_code_[0] == 0) {
|
||||
this->rolling_code_[1]++;
|
||||
this->pref_.save(&this->rolling_code_[1]);
|
||||
// must make sure it's saved immediately
|
||||
global_preferences->sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PacketTransport::loop() {
|
||||
if (this->resend_ping_key_)
|
||||
this->send_ping_pong_request_();
|
||||
if (this->updated_) {
|
||||
this->send_data_(this->resend_data_);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketTransport::send_ping_pong_request_() {
|
||||
if (!this->ping_pong_enable_ || !this->should_send())
|
||||
return;
|
||||
this->ping_key_ = random_uint32();
|
||||
this->ping_header_.clear();
|
||||
add(this->ping_header_, MAGIC_PING);
|
||||
add(this->ping_header_, this->name_);
|
||||
add(this->ping_header_, this->ping_key_);
|
||||
this->send_packet(this->ping_header_);
|
||||
this->resend_ping_key_ = false;
|
||||
ESP_LOGV(TAG, "Sent new ping request %08X", (unsigned) this->ping_key_);
|
||||
}
|
||||
} // namespace packet_transport
|
||||
} // namespace esphome
|
155
esphome/components/packet_transport/packet_transport.h
Normal file
155
esphome/components/packet_transport/packet_transport.h
Normal file
@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* Providing packet encoding functions for exchanging data with a remote host.
|
||||
*
|
||||
* A transport is required to send the data; this is provided by a child class.
|
||||
* The child class should implement the virtual functions send_packet_ and get_max_packet_size_.
|
||||
* On receipt of a data packet, it should call `this->process_()` with the data.
|
||||
*/
|
||||
|
||||
namespace esphome {
|
||||
namespace packet_transport {
|
||||
|
||||
struct Provider {
|
||||
std::vector<uint8_t> encryption_key;
|
||||
const char *name;
|
||||
uint32_t last_code[2];
|
||||
};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
struct Sensor {
|
||||
sensor::Sensor *sensor;
|
||||
const char *id;
|
||||
bool updated;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
struct BinarySensor {
|
||||
binary_sensor::BinarySensor *sensor;
|
||||
const char *id;
|
||||
bool updated;
|
||||
};
|
||||
#endif
|
||||
|
||||
class PacketTransport : public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void add_sensor(const char *id, sensor::Sensor *sensor) {
|
||||
Sensor st{sensor, id, true};
|
||||
this->sensors_.push_back(st);
|
||||
}
|
||||
void add_remote_sensor(const char *hostname, const char *remote_id, sensor::Sensor *sensor) {
|
||||
this->add_provider(hostname);
|
||||
this->remote_sensors_[hostname][remote_id] = sensor;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void add_binary_sensor(const char *id, binary_sensor::BinarySensor *sensor) {
|
||||
BinarySensor st{sensor, id, true};
|
||||
this->binary_sensors_.push_back(st);
|
||||
}
|
||||
|
||||
void add_remote_binary_sensor(const char *hostname, const char *remote_id, binary_sensor::BinarySensor *sensor) {
|
||||
this->add_provider(hostname);
|
||||
this->remote_binary_sensors_[hostname][remote_id] = sensor;
|
||||
}
|
||||
#endif
|
||||
|
||||
void add_provider(const char *hostname) {
|
||||
if (this->providers_.count(hostname) == 0) {
|
||||
Provider provider;
|
||||
provider.encryption_key = std::vector<uint8_t>{};
|
||||
provider.last_code[0] = 0;
|
||||
provider.last_code[1] = 0;
|
||||
provider.name = hostname;
|
||||
this->providers_[hostname] = provider;
|
||||
#ifdef USE_SENSOR
|
||||
this->remote_sensors_[hostname] = std::map<std::string, sensor::Sensor *>();
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
this->remote_binary_sensors_[hostname] = std::map<std::string, binary_sensor::BinarySensor *>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void set_encryption_key(std::vector<uint8_t> key) { this->encryption_key_ = std::move(key); }
|
||||
void set_rolling_code_enable(bool enable) { this->rolling_code_enable_ = enable; }
|
||||
void set_ping_pong_enable(bool enable) { this->ping_pong_enable_ = enable; }
|
||||
void set_ping_pong_recycle_time(uint32_t recycle_time) { this->ping_pong_recyle_time_ = recycle_time; }
|
||||
void set_provider_encryption(const char *name, std::vector<uint8_t> key) {
|
||||
this->providers_[name].encryption_key = std::move(key);
|
||||
}
|
||||
void set_platform_name(const char *name) { this->platform_name_ = name; }
|
||||
|
||||
protected:
|
||||
// child classes must implement this
|
||||
virtual void send_packet(std::vector<uint8_t> &buf) const = 0;
|
||||
virtual size_t get_max_packet_size() = 0;
|
||||
virtual bool should_send() { return true; }
|
||||
|
||||
// to be called by child classes when a data packet is received.
|
||||
void process_(std::vector<uint8_t> &data);
|
||||
void send_data_(bool all);
|
||||
void flush_();
|
||||
void add_data_(uint8_t key, const char *id, float data);
|
||||
void add_data_(uint8_t key, const char *id, uint32_t data);
|
||||
void increment_code_();
|
||||
void add_binary_data_(uint8_t key, const char *id, bool data);
|
||||
void init_data_();
|
||||
|
||||
bool updated_{};
|
||||
uint32_t ping_key_{};
|
||||
uint32_t rolling_code_[2]{};
|
||||
bool rolling_code_enable_{};
|
||||
bool ping_pong_enable_{};
|
||||
uint32_t ping_pong_recyle_time_{};
|
||||
uint32_t last_key_time_{};
|
||||
bool resend_ping_key_{};
|
||||
bool resend_data_{};
|
||||
const char *name_{};
|
||||
ESPPreferenceObject pref_{};
|
||||
|
||||
std::vector<uint8_t> encryption_key_{};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
std::vector<Sensor> sensors_{};
|
||||
std::map<std::string, std::map<std::string, sensor::Sensor *>> remote_sensors_{};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::vector<BinarySensor> binary_sensors_{};
|
||||
std::map<std::string, std::map<std::string, binary_sensor::BinarySensor *>> remote_binary_sensors_{};
|
||||
#endif
|
||||
|
||||
std::map<std::string, Provider> providers_{};
|
||||
std::vector<uint8_t> ping_header_{};
|
||||
std::vector<uint8_t> header_{};
|
||||
std::vector<uint8_t> data_{};
|
||||
std::map<const char *, uint32_t> ping_keys_{};
|
||||
const char *platform_name_{""};
|
||||
void add_key_(const char *name, uint32_t key);
|
||||
void send_ping_pong_request_();
|
||||
void process_ping_request_(const char *name, uint8_t *ptr, size_t len);
|
||||
|
||||
inline bool is_encrypted_() { return !this->encryption_key_.empty(); }
|
||||
};
|
||||
|
||||
} // namespace packet_transport
|
||||
} // namespace esphome
|
19
esphome/components/packet_transport/sensor.py
Normal file
19
esphome/components/packet_transport/sensor.py
Normal file
@ -0,0 +1,19 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.sensor import new_sensor, sensor_schema
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from . import (
|
||||
CONF_PROVIDER,
|
||||
CONF_REMOTE_ID,
|
||||
CONF_TRANSPORT_ID,
|
||||
packet_transport_sensor_schema,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = packet_transport_sensor_schema(sensor_schema())
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await new_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_sensor(config[CONF_PROVIDER], remote_id, var))
|
@ -1,164 +1,162 @@
|
||||
import hashlib
|
||||
|
||||
from esphome import automation
|
||||
from esphome.automation import Trigger
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.api import CONF_ENCRYPTION
|
||||
from esphome.components.binary_sensor import BinarySensor
|
||||
from esphome.components.sensor import Sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
from esphome.components.packet_transport import (
|
||||
CONF_BINARY_SENSORS,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_KEY,
|
||||
CONF_NAME,
|
||||
CONF_PORT,
|
||||
CONF_ENCRYPTION,
|
||||
CONF_PING_PONG_ENABLE,
|
||||
CONF_PROVIDERS,
|
||||
CONF_ROLLING_CODE_ENABLE,
|
||||
CONF_SENSORS,
|
||||
)
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_DATA, CONF_ID, CONF_PORT, CONF_TRIGGER_ID
|
||||
from esphome.core import Lambda
|
||||
from esphome.cpp_generator import ExpressionStatement, MockObj
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["socket", "xxtea"]
|
||||
AUTO_LOAD = ["socket"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
udp_ns = cg.esphome_ns.namespace("udp")
|
||||
UDPComponent = udp_ns.class_("UDPComponent", cg.PollingComponent)
|
||||
UDPComponent = udp_ns.class_("UDPComponent", cg.Component)
|
||||
UDPWriteAction = udp_ns.class_("UDPWriteAction", automation.Action)
|
||||
trigger_args = cg.std_vector.template(cg.uint8)
|
||||
|
||||
CONF_BROADCAST = "broadcast"
|
||||
CONF_BROADCAST_ID = "broadcast_id"
|
||||
CONF_ADDRESSES = "addresses"
|
||||
CONF_LISTEN_ADDRESS = "listen_address"
|
||||
CONF_PROVIDER = "provider"
|
||||
CONF_PROVIDERS = "providers"
|
||||
CONF_REMOTE_ID = "remote_id"
|
||||
CONF_UDP_ID = "udp_id"
|
||||
CONF_PING_PONG_ENABLE = "ping_pong_enable"
|
||||
CONF_PING_PONG_RECYCLE_TIME = "ping_pong_recycle_time"
|
||||
CONF_ROLLING_CODE_ENABLE = "rolling_code_enable"
|
||||
CONF_ON_RECEIVE = "on_receive"
|
||||
CONF_LISTEN_PORT = "listen_port"
|
||||
CONF_BROADCAST_PORT = "broadcast_port"
|
||||
|
||||
|
||||
def sensor_validation(cls: MockObjClass):
|
||||
return cv.maybe_simple_value(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(cls),
|
||||
cv.Optional(CONF_BROADCAST_ID): cv.validate_id_name,
|
||||
}
|
||||
),
|
||||
key=CONF_ID,
|
||||
)
|
||||
|
||||
|
||||
ENCRYPTION_SCHEMA = {
|
||||
cv.Optional(CONF_ENCRYPTION): cv.maybe_simple_value(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_KEY): cv.string,
|
||||
}
|
||||
),
|
||||
key=CONF_KEY,
|
||||
)
|
||||
}
|
||||
|
||||
PROVIDER_SCHEMA = cv.Schema(
|
||||
UDP_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
}
|
||||
).extend(ENCRYPTION_SCHEMA)
|
||||
|
||||
|
||||
def validate_(config):
|
||||
if CONF_ENCRYPTION in config:
|
||||
if CONF_SENSORS not in config and CONF_BINARY_SENSORS not in config:
|
||||
raise cv.Invalid("No sensors or binary sensors to encrypt")
|
||||
elif config[CONF_ROLLING_CODE_ENABLE]:
|
||||
raise cv.Invalid("Rolling code requires an encryption key")
|
||||
if config[CONF_PING_PONG_ENABLE]:
|
||||
if not any(CONF_ENCRYPTION in p for p in config.get(CONF_PROVIDERS) or ()):
|
||||
raise cv.Invalid("Ping-pong requires at least one encrypted provider")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.polling_component_schema("15s")
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(UDPComponent),
|
||||
cv.Optional(CONF_PORT, default=18511): cv.port,
|
||||
cv.Optional(
|
||||
CONF_LISTEN_ADDRESS, default="255.255.255.255"
|
||||
): cv.ipv4address_multi_broadcast,
|
||||
cv.Optional(CONF_ADDRESSES, default=["255.255.255.255"]): cv.ensure_list(
|
||||
cv.ipv4address,
|
||||
),
|
||||
cv.Optional(CONF_ROLLING_CODE_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PING_PONG_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_PING_PONG_RECYCLE_TIME, default="600s"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Optional(CONF_SENSORS): cv.ensure_list(sensor_validation(Sensor)),
|
||||
cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(
|
||||
sensor_validation(BinarySensor)
|
||||
),
|
||||
cv.Optional(CONF_PROVIDERS): cv.ensure_list(PROVIDER_SCHEMA),
|
||||
},
|
||||
)
|
||||
.extend(ENCRYPTION_SCHEMA),
|
||||
validate_,
|
||||
)
|
||||
|
||||
SENSOR_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_REMOTE_ID): cv.string_strict,
|
||||
cv.Required(CONF_PROVIDER): cv.valid_name,
|
||||
cv.GenerateID(CONF_UDP_ID): cv.use_id(UDPComponent),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def require_internal_with_name(config):
|
||||
if CONF_NAME in config and CONF_INTERNAL not in config:
|
||||
raise cv.Invalid("Must provide internal: config when using name:")
|
||||
return config
|
||||
def is_relocated(option):
|
||||
def validator(value):
|
||||
raise cv.Invalid(
|
||||
f"The '{option}' option should now be configured in the 'packet_transport' component"
|
||||
)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def hash_encryption_key(config: dict):
|
||||
return list(hashlib.sha256(config[CONF_KEY].encode()).digest())
|
||||
RELOCATED = {
|
||||
cv.Optional(x): is_relocated(x)
|
||||
for x in (
|
||||
CONF_PROVIDERS,
|
||||
CONF_ENCRYPTION,
|
||||
CONF_PING_PONG_ENABLE,
|
||||
CONF_ROLLING_CODE_ENABLE,
|
||||
CONF_SENSORS,
|
||||
CONF_BINARY_SENSORS,
|
||||
)
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(UDPComponent),
|
||||
cv.Optional(CONF_PORT, default=18511): cv.Any(
|
||||
cv.port,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_LISTEN_PORT): cv.port,
|
||||
cv.Required(CONF_BROADCAST_PORT): cv.port,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_LISTEN_ADDRESS, default="255.255.255.255"
|
||||
): cv.ipv4address_multi_broadcast,
|
||||
cv.Optional(CONF_ADDRESSES, default=["255.255.255.255"]): cv.ensure_list(
|
||||
cv.ipv4address,
|
||||
),
|
||||
cv.Optional(CONF_ON_RECEIVE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
Trigger.template(trigger_args)
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(RELOCATED)
|
||||
|
||||
|
||||
async def register_udp_client(var, config):
|
||||
udp_var = await cg.get_variable(config[CONF_UDP_ID])
|
||||
cg.add(var.set_parent(udp_var))
|
||||
return udp_var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_UDP")
|
||||
cg.add_global(udp_ns.using)
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_rolling_code_enable(config[CONF_ROLLING_CODE_ENABLE]))
|
||||
cg.add(var.set_ping_pong_enable(config[CONF_PING_PONG_ENABLE]))
|
||||
cg.add(
|
||||
var.set_ping_pong_recycle_time(
|
||||
config[CONF_PING_PONG_RECYCLE_TIME].total_seconds
|
||||
)
|
||||
)
|
||||
for sens_conf in config.get(CONF_SENSORS, ()):
|
||||
sens_id = sens_conf[CONF_ID]
|
||||
sensor = await cg.get_variable(sens_id)
|
||||
bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id)
|
||||
cg.add(var.add_sensor(bcst_id, sensor))
|
||||
for sens_conf in config.get(CONF_BINARY_SENSORS, ()):
|
||||
sens_id = sens_conf[CONF_ID]
|
||||
sensor = await cg.get_variable(sens_id)
|
||||
bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id)
|
||||
cg.add(var.add_binary_sensor(bcst_id, sensor))
|
||||
var = await cg.register_component(var, config)
|
||||
conf_port = config[CONF_PORT]
|
||||
if isinstance(conf_port, int):
|
||||
cg.add(var.set_listen_port(conf_port))
|
||||
cg.add(var.set_broadcast_port(conf_port))
|
||||
else:
|
||||
cg.add(var.set_listen_port(conf_port[CONF_LISTEN_PORT]))
|
||||
cg.add(var.set_broadcast_port(conf_port[CONF_BROADCAST_PORT]))
|
||||
if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255":
|
||||
cg.add(var.set_listen_address(listen_address))
|
||||
for address in config[CONF_ADDRESSES]:
|
||||
cg.add(var.add_address(str(address)))
|
||||
if on_receive := config.get(CONF_ON_RECEIVE):
|
||||
on_receive = on_receive[0]
|
||||
trigger = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID])
|
||||
trigger = await automation.build_automation(
|
||||
trigger, [(trigger_args, "data")], on_receive
|
||||
)
|
||||
trigger = Lambda(str(ExpressionStatement(trigger.trigger(MockObj("data")))))
|
||||
trigger = await cg.process_lambda(trigger, [(trigger_args, "data")])
|
||||
cg.add(var.add_listener(trigger))
|
||||
cg.add(var.set_should_listen())
|
||||
|
||||
if encryption := config.get(CONF_ENCRYPTION):
|
||||
cg.add(var.set_encryption_key(hash_encryption_key(encryption)))
|
||||
|
||||
for provider in config.get(CONF_PROVIDERS, ()):
|
||||
name = provider[CONF_NAME]
|
||||
cg.add(var.add_provider(name))
|
||||
if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255":
|
||||
cg.add(var.set_listen_address(listen_address))
|
||||
if encryption := provider.get(CONF_ENCRYPTION):
|
||||
cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption)))
|
||||
def validate_raw_data(value):
|
||||
if isinstance(value, str):
|
||||
return value.encode("utf-8")
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([cv.hex_uint8_t])(value)
|
||||
raise cv.Invalid(
|
||||
"data must either be a string wrapped in quotes or a list of bytes"
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"udp.write",
|
||||
UDPWriteAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(UDPComponent),
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
},
|
||||
key=CONF_DATA,
|
||||
),
|
||||
)
|
||||
async def udp_write_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
udp_var = await cg.get_variable(config[CONF_ID])
|
||||
await cg.register_parented(var, udp_var)
|
||||
cg.add(udp_var.set_should_broadcast())
|
||||
data = config[CONF_DATA]
|
||||
if isinstance(data, bytes):
|
||||
data = list(data)
|
||||
|
||||
if cg.is_template(data):
|
||||
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_data_template(templ))
|
||||
else:
|
||||
cg.add(var.set_data_static(data))
|
||||
return var
|
||||
|
38
esphome/components/udp/automation.h
Normal file
38
esphome/components/udp/automation.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "udp_component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace udp {
|
||||
|
||||
template<typename... Ts> class UDPWriteAction : public Action<Ts...>, public Parented<UDPComponent> {
|
||||
public:
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
this->static_ = false;
|
||||
}
|
||||
void set_data_static(const std::vector<uint8_t> &data) {
|
||||
this->data_static_ = data;
|
||||
this->static_ = true;
|
||||
}
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->static_) {
|
||||
this->parent_->send_packet(this->data_static_);
|
||||
} else {
|
||||
auto val = this->data_func_(x...);
|
||||
this->parent_->send_packet(val);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool static_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
};
|
||||
|
||||
} // namespace udp
|
||||
} // namespace esphome
|
@ -1,27 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.config_validation import All, has_at_least_one_key
|
||||
from esphome.const import CONF_ID
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from . import (
|
||||
CONF_PROVIDER,
|
||||
CONF_REMOTE_ID,
|
||||
CONF_UDP_ID,
|
||||
SENSOR_SCHEMA,
|
||||
require_internal_with_name,
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
"The 'udp.binary_sensor' component has been migrated to the 'packet_transport.binary_sensor' component."
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["udp"]
|
||||
|
||||
CONFIG_SCHEMA = All(
|
||||
binary_sensor.binary_sensor_schema().extend(SENSOR_SCHEMA),
|
||||
has_at_least_one_key(CONF_ID, CONF_REMOTE_ID),
|
||||
require_internal_with_name,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
comp = await cg.get_variable(config[CONF_UDP_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))
|
||||
|
29
esphome/components/udp/packet_transport/__init__.py
Normal file
29
esphome/components/udp/packet_transport/__init__.py
Normal file
@ -0,0 +1,29 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.api import CONF_ENCRYPTION
|
||||
from esphome.components.packet_transport import (
|
||||
CONF_PING_PONG_ENABLE,
|
||||
PacketTransport,
|
||||
new_packet_transport,
|
||||
transport_schema,
|
||||
)
|
||||
from esphome.const import CONF_BINARY_SENSORS, CONF_SENSORS
|
||||
from esphome.cpp_types import PollingComponent
|
||||
|
||||
from .. import UDP_SCHEMA, register_udp_client, udp_ns
|
||||
|
||||
UDPTransport = udp_ns.class_("UDPTransport", PacketTransport, PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = transport_schema(UDPTransport).extend(UDP_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var, providers = await new_packet_transport(config)
|
||||
udp_var = await register_udp_client(var, config)
|
||||
if CONF_ENCRYPTION in config or providers:
|
||||
cg.add(udp_var.set_should_listen())
|
||||
if (
|
||||
config[CONF_PING_PONG_ENABLE]
|
||||
or config.get(CONF_SENSORS, ())
|
||||
or config.get(CONF_BINARY_SENSORS, ())
|
||||
):
|
||||
cg.add(udp_var.set_should_broadcast())
|
36
esphome/components/udp/packet_transport/udp_transport.cpp
Normal file
36
esphome/components/udp/packet_transport/udp_transport.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "udp_transport.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace udp {
|
||||
|
||||
static const char *const TAG = "udp_transport";
|
||||
|
||||
bool UDPTransport::should_send() { return this->should_broadcast_ && network::is_connected(); }
|
||||
void UDPTransport::setup() {
|
||||
PacketTransport::setup();
|
||||
this->should_broadcast_ = this->ping_pong_enable_;
|
||||
#ifdef USE_SENSOR
|
||||
this->should_broadcast_ |= !this->sensors_.empty();
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
this->should_broadcast_ |= !this->binary_sensors_.empty();
|
||||
#endif
|
||||
if (this->should_broadcast_)
|
||||
this->parent_->set_should_broadcast();
|
||||
if (!this->providers_.empty() || this->is_encrypted_()) {
|
||||
this->parent_->add_listener([this](std::vector<uint8_t> &buf) { this->process_(buf); });
|
||||
}
|
||||
}
|
||||
|
||||
void UDPTransport::update() {
|
||||
PacketTransport::update();
|
||||
this->updated_ = true;
|
||||
this->resend_data_ = this->should_broadcast_;
|
||||
}
|
||||
|
||||
void UDPTransport::send_packet(std::vector<uint8_t> &buf) const { this->parent_->send_packet(buf); }
|
||||
} // namespace udp
|
||||
} // namespace esphome
|
26
esphome/components/udp/packet_transport/udp_transport.h
Normal file
26
esphome/components/udp/packet_transport/udp_transport.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "../udp_component.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/packet_transport/packet_transport.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace udp {
|
||||
|
||||
class UDPTransport : public packet_transport::PacketTransport, public Parented<UDPComponent> {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
protected:
|
||||
void send_packet(std::vector<uint8_t> &buf) const override;
|
||||
bool should_send() override;
|
||||
bool should_broadcast_{false};
|
||||
size_t get_max_packet_size() override { return MAX_PACKET_SIZE; }
|
||||
};
|
||||
|
||||
} // namespace udp
|
||||
} // namespace esphome
|
@ -1,27 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.sensor import new_sensor, sensor_schema
|
||||
from esphome.config_validation import All, has_at_least_one_key
|
||||
from esphome.const import CONF_ID
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from . import (
|
||||
CONF_PROVIDER,
|
||||
CONF_REMOTE_ID,
|
||||
CONF_UDP_ID,
|
||||
SENSOR_SCHEMA,
|
||||
require_internal_with_name,
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
"The 'udp.sensor' component has been migrated to the 'packet_transport.sensor' component."
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["udp"]
|
||||
|
||||
CONFIG_SCHEMA = All(
|
||||
sensor_schema().extend(SENSOR_SCHEMA),
|
||||
has_at_least_one_key(CONF_ID, CONF_REMOTE_ID),
|
||||
require_internal_with_name,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await new_sensor(config)
|
||||
comp = await cg.get_variable(config[CONF_UDP_ID])
|
||||
remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID))
|
||||
cg.add(comp.add_remote_sensor(config[CONF_PROVIDER], remote_id, var))
|
||||
|
@ -1,164 +1,24 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_NETWORK
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "udp_component.h"
|
||||
|
||||
#include "esphome/components/xxtea/xxtea.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace udp {
|
||||
|
||||
/**
|
||||
* Structure of a data packet; everything is little-endian
|
||||
*
|
||||
* --- In clear text ---
|
||||
* MAGIC_NUMBER: 16 bits
|
||||
* host name length: 1 byte
|
||||
* host name: (length) bytes
|
||||
* padding: 0 or more null bytes to a 4 byte boundary
|
||||
*
|
||||
* --- Encrypted (if key set) ----
|
||||
* DATA_KEY: 1 byte: OR ROLLING_CODE_KEY:
|
||||
* Rolling code (if enabled): 8 bytes
|
||||
* Ping keys: if any
|
||||
* repeat:
|
||||
* PING_KEY: 1 byte
|
||||
* ping code: 4 bytes
|
||||
* Sensors:
|
||||
* repeat:
|
||||
* SENSOR_KEY: 1 byte
|
||||
* float value: 4 bytes
|
||||
* name length: 1 byte
|
||||
* name
|
||||
* Binary Sensors:
|
||||
* repeat:
|
||||
* BINARY_SENSOR_KEY: 1 byte
|
||||
* bool value: 1 bytes
|
||||
* name length: 1 byte
|
||||
* name
|
||||
*
|
||||
* Padded to a 4 byte boundary with nulls
|
||||
*
|
||||
* Structure of a ping request packet:
|
||||
* --- In clear text ---
|
||||
* MAGIC_PING: 16 bits
|
||||
* host name length: 1 byte
|
||||
* host name: (length) bytes
|
||||
* Ping key (4 bytes)
|
||||
*
|
||||
*/
|
||||
static const char *const TAG = "udp";
|
||||
|
||||
static size_t round4(size_t value) { return (value + 3) & ~3; }
|
||||
|
||||
union FuData {
|
||||
uint32_t u32;
|
||||
float f32;
|
||||
};
|
||||
|
||||
static const size_t MAX_PACKET_SIZE = 508;
|
||||
static const uint16_t MAGIC_NUMBER = 0x4553;
|
||||
static const uint16_t MAGIC_PING = 0x5048;
|
||||
static const uint32_t PREF_HASH = 0x45535043;
|
||||
enum DataKey {
|
||||
ZERO_FILL_KEY,
|
||||
DATA_KEY,
|
||||
SENSOR_KEY,
|
||||
BINARY_SENSOR_KEY,
|
||||
PING_KEY,
|
||||
ROLLING_CODE_KEY,
|
||||
};
|
||||
|
||||
static const size_t MAX_PING_KEYS = 4;
|
||||
|
||||
static inline void add(std::vector<uint8_t> &vec, uint32_t data) {
|
||||
vec.push_back(data & 0xFF);
|
||||
vec.push_back((data >> 8) & 0xFF);
|
||||
vec.push_back((data >> 16) & 0xFF);
|
||||
vec.push_back((data >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
static inline uint32_t get_uint32(uint8_t *&buf) {
|
||||
uint32_t data = *buf++;
|
||||
data += *buf++ << 8;
|
||||
data += *buf++ << 16;
|
||||
data += *buf++ << 24;
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline uint16_t get_uint16(uint8_t *&buf) {
|
||||
uint16_t data = *buf++;
|
||||
data += *buf++ << 8;
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline void add(std::vector<uint8_t> &vec, uint8_t data) { vec.push_back(data); }
|
||||
static inline void add(std::vector<uint8_t> &vec, uint16_t data) {
|
||||
vec.push_back((uint8_t) data);
|
||||
vec.push_back((uint8_t) (data >> 8));
|
||||
}
|
||||
static inline void add(std::vector<uint8_t> &vec, DataKey data) { vec.push_back(data); }
|
||||
static void add(std::vector<uint8_t> &vec, const char *str) {
|
||||
auto len = strlen(str);
|
||||
vec.push_back(len);
|
||||
for (size_t i = 0; i != len; i++) {
|
||||
vec.push_back(*str++);
|
||||
}
|
||||
}
|
||||
|
||||
void UDPComponent::setup() {
|
||||
this->name_ = App.get_name().c_str();
|
||||
if (strlen(this->name_) > 255) {
|
||||
this->mark_failed();
|
||||
this->status_set_error("Device name exceeds 255 chars");
|
||||
return;
|
||||
}
|
||||
this->resend_ping_key_ = this->ping_pong_enable_;
|
||||
// restore the upper 32 bits of the rolling code, increment and save.
|
||||
this->pref_ = global_preferences->make_preference<uint32_t>(PREF_HASH, true);
|
||||
this->pref_.load(&this->rolling_code_[1]);
|
||||
this->rolling_code_[1]++;
|
||||
this->pref_.save(&this->rolling_code_[1]);
|
||||
this->ping_key_ = random_uint32();
|
||||
ESP_LOGV(TAG, "Rolling code incremented, upper part now %u", (unsigned) this->rolling_code_[1]);
|
||||
#ifdef USE_SENSOR
|
||||
for (auto &sensor : this->sensors_) {
|
||||
sensor.sensor->add_on_state_callback([this, &sensor](float x) {
|
||||
this->updated_ = true;
|
||||
sensor.updated = true;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto &sensor : this->binary_sensors_) {
|
||||
sensor.sensor->add_on_state_callback([this, &sensor](bool value) {
|
||||
this->updated_ = true;
|
||||
sensor.updated = true;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
this->should_send_ = this->ping_pong_enable_;
|
||||
#ifdef USE_SENSOR
|
||||
this->should_send_ |= !this->sensors_.empty();
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
this->should_send_ |= !this->binary_sensors_.empty();
|
||||
#endif
|
||||
this->should_listen_ = !this->providers_.empty() || this->is_encrypted_();
|
||||
// initialise the header. This is invariant.
|
||||
add(this->header_, MAGIC_NUMBER);
|
||||
add(this->header_, this->name_);
|
||||
// pad to a multiple of 4 bytes
|
||||
while (this->header_.size() & 0x3)
|
||||
this->header_.push_back(0);
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
for (const auto &address : this->addresses_) {
|
||||
struct sockaddr saddr {};
|
||||
socket::set_sockaddr(&saddr, sizeof(saddr), address, this->port_);
|
||||
socket::set_sockaddr(&saddr, sizeof(saddr), address, this->broadcast_port_);
|
||||
this->sockaddrs_.push_back(saddr);
|
||||
}
|
||||
// set up broadcast socket
|
||||
if (this->should_send_) {
|
||||
if (this->should_broadcast_) {
|
||||
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (this->broadcast_socket_ == nullptr) {
|
||||
this->mark_failed();
|
||||
@ -202,14 +62,14 @@ void UDPComponent::setup() {
|
||||
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = ESPHOME_INADDR_ANY;
|
||||
server.sin_port = htons(this->port_);
|
||||
server.sin_port = htons(this->listen_port_);
|
||||
|
||||
if (this->listen_address_.has_value()) {
|
||||
struct ip_mreq imreq = {};
|
||||
imreq.imr_interface.s_addr = ESPHOME_INADDR_ANY;
|
||||
inet_aton(this->listen_address_.value().str().c_str(), &imreq.imr_multiaddr);
|
||||
server.sin_addr.s_addr = imreq.imr_multiaddr.s_addr;
|
||||
ESP_LOGV(TAG, "Join multicast %s", this->listen_address_.value().str().c_str());
|
||||
ESP_LOGD(TAG, "Join multicast %s", this->listen_address_.value().str().c_str());
|
||||
err = this->listen_socket_->setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(imreq));
|
||||
if (err < 0) {
|
||||
ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno);
|
||||
@ -236,341 +96,48 @@ void UDPComponent::setup() {
|
||||
this->ipaddrs_.push_back(ipaddr);
|
||||
}
|
||||
if (this->should_listen_)
|
||||
this->udp_client_.begin(this->port_);
|
||||
this->udp_client_.begin(this->listen_port_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UDPComponent::init_data_() {
|
||||
this->data_.clear();
|
||||
if (this->rolling_code_enable_) {
|
||||
add(this->data_, ROLLING_CODE_KEY);
|
||||
add(this->data_, this->rolling_code_[0]);
|
||||
add(this->data_, this->rolling_code_[1]);
|
||||
this->increment_code_();
|
||||
} else {
|
||||
add(this->data_, DATA_KEY);
|
||||
}
|
||||
for (auto pkey : this->ping_keys_) {
|
||||
add(this->data_, PING_KEY);
|
||||
add(this->data_, pkey.second);
|
||||
}
|
||||
}
|
||||
|
||||
void UDPComponent::flush_() {
|
||||
if (!network::is_connected() || this->data_.empty())
|
||||
return;
|
||||
uint32_t buffer[MAX_PACKET_SIZE / 4];
|
||||
memset(buffer, 0, sizeof buffer);
|
||||
// len must be a multiple of 4
|
||||
auto header_len = round4(this->header_.size()) / 4;
|
||||
auto len = round4(data_.size()) / 4;
|
||||
memcpy(buffer, this->header_.data(), this->header_.size());
|
||||
memcpy(buffer + header_len, this->data_.data(), this->data_.size());
|
||||
if (this->is_encrypted_()) {
|
||||
xxtea::encrypt(buffer + header_len, len, (uint32_t *) this->encryption_key_.data());
|
||||
}
|
||||
auto total_len = (header_len + len) * 4;
|
||||
this->send_packet_(buffer, total_len);
|
||||
}
|
||||
|
||||
void UDPComponent::add_binary_data_(uint8_t key, const char *id, bool data) {
|
||||
auto len = 1 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > MAX_PACKET_SIZE) {
|
||||
this->flush_();
|
||||
}
|
||||
add(this->data_, key);
|
||||
add(this->data_, (uint8_t) data);
|
||||
add(this->data_, id);
|
||||
}
|
||||
void UDPComponent::add_data_(uint8_t key, const char *id, float data) {
|
||||
FuData udata{.f32 = data};
|
||||
this->add_data_(key, id, udata.u32);
|
||||
}
|
||||
|
||||
void UDPComponent::add_data_(uint8_t key, const char *id, uint32_t data) {
|
||||
auto len = 4 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > MAX_PACKET_SIZE) {
|
||||
this->flush_();
|
||||
}
|
||||
add(this->data_, key);
|
||||
add(this->data_, data);
|
||||
add(this->data_, id);
|
||||
}
|
||||
void UDPComponent::send_data_(bool all) {
|
||||
if (!this->should_send_ || !network::is_connected())
|
||||
return;
|
||||
this->init_data_();
|
||||
#ifdef USE_SENSOR
|
||||
for (auto &sensor : this->sensors_) {
|
||||
if (all || sensor.updated) {
|
||||
sensor.updated = false;
|
||||
this->add_data_(SENSOR_KEY, sensor.id, sensor.sensor->get_state());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto &sensor : this->binary_sensors_) {
|
||||
if (all || sensor.updated) {
|
||||
sensor.updated = false;
|
||||
this->add_binary_data_(BINARY_SENSOR_KEY, sensor.id, sensor.sensor->state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
this->flush_();
|
||||
this->updated_ = false;
|
||||
this->resend_data_ = false;
|
||||
}
|
||||
|
||||
void UDPComponent::update() {
|
||||
this->updated_ = true;
|
||||
this->resend_data_ = this->should_send_;
|
||||
auto now = millis() / 1000;
|
||||
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
|
||||
this->resend_ping_key_ = this->ping_pong_enable_;
|
||||
this->last_key_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void UDPComponent::loop() {
|
||||
uint8_t buf[MAX_PACKET_SIZE];
|
||||
auto buf = std::vector<uint8_t>(MAX_PACKET_SIZE);
|
||||
if (this->should_listen_) {
|
||||
for (;;) {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
auto len = this->listen_socket_->read(buf, sizeof(buf));
|
||||
auto len = this->listen_socket_->read(buf.data(), buf.size());
|
||||
#endif
|
||||
#ifdef USE_SOCKET_IMPL_LWIP_TCP
|
||||
auto len = this->udp_client_.parsePacket();
|
||||
if (len > 0)
|
||||
len = this->udp_client_.read(buf, sizeof(buf));
|
||||
len = this->udp_client_.read(buf.data(), buf.size());
|
||||
#endif
|
||||
if (len > 0) {
|
||||
this->process_(buf, len);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
if (len <= 0)
|
||||
break;
|
||||
buf.resize(len);
|
||||
ESP_LOGV(TAG, "Received packet of length %zu", len);
|
||||
this->packet_listeners_.call(buf);
|
||||
}
|
||||
}
|
||||
if (this->resend_ping_key_)
|
||||
this->send_ping_pong_request_();
|
||||
if (this->updated_) {
|
||||
this->send_data_(this->resend_data_);
|
||||
}
|
||||
}
|
||||
|
||||
void UDPComponent::add_key_(const char *name, uint32_t key) {
|
||||
if (!this->is_encrypted_())
|
||||
return;
|
||||
if (this->ping_keys_.count(name) == 0 && this->ping_keys_.size() == MAX_PING_KEYS) {
|
||||
ESP_LOGW(TAG, "Ping key from %s discarded", name);
|
||||
return;
|
||||
}
|
||||
this->ping_keys_[name] = key;
|
||||
this->resend_data_ = true;
|
||||
ESP_LOGV(TAG, "Ping key from %s now %X", name, (unsigned) key);
|
||||
}
|
||||
|
||||
void UDPComponent::process_ping_request_(const char *name, uint8_t *ptr, size_t len) {
|
||||
if (len != 4) {
|
||||
ESP_LOGW(TAG, "Bad ping request");
|
||||
return;
|
||||
}
|
||||
auto key = get_uint32(ptr);
|
||||
this->add_key_(name, key);
|
||||
ESP_LOGV(TAG, "Updated ping key for %s to %08X", name, (unsigned) key);
|
||||
}
|
||||
|
||||
static bool process_rolling_code(Provider &provider, uint8_t *&buf, const uint8_t *end) {
|
||||
if (end - buf < 8)
|
||||
return false;
|
||||
auto code0 = get_uint32(buf);
|
||||
auto code1 = get_uint32(buf);
|
||||
if (code1 < provider.last_code[1] || (code1 == provider.last_code[1] && code0 <= provider.last_code[0])) {
|
||||
ESP_LOGW(TAG, "Rolling code for %s %08lX:%08lX is old", provider.name, (unsigned long) code1,
|
||||
(unsigned long) code0);
|
||||
return false;
|
||||
}
|
||||
provider.last_code[0] = code0;
|
||||
provider.last_code[1] = code1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a received packet
|
||||
*/
|
||||
void UDPComponent::process_(uint8_t *buf, const size_t len) {
|
||||
auto ping_key_seen = !this->ping_pong_enable_;
|
||||
if (len < 8) {
|
||||
ESP_LOGV(TAG, "Bad length %zu", len);
|
||||
return;
|
||||
}
|
||||
char namebuf[256]{};
|
||||
uint8_t byte;
|
||||
uint8_t *start_ptr = buf;
|
||||
const uint8_t *end = buf + len;
|
||||
FuData rdata{};
|
||||
auto magic = get_uint16(buf);
|
||||
if (magic != MAGIC_NUMBER && magic != MAGIC_PING) {
|
||||
ESP_LOGV(TAG, "Bad magic %X", magic);
|
||||
return;
|
||||
}
|
||||
|
||||
auto hlen = *buf++;
|
||||
if (hlen > len - 3) {
|
||||
ESP_LOGV(TAG, "Bad hostname length %u > %zu", hlen, len - 3);
|
||||
return;
|
||||
}
|
||||
memcpy(namebuf, buf, hlen);
|
||||
if (strcmp(this->name_, namebuf) == 0) {
|
||||
ESP_LOGV(TAG, "Ignoring our own data");
|
||||
return;
|
||||
}
|
||||
buf += hlen;
|
||||
if (magic == MAGIC_PING) {
|
||||
this->process_ping_request_(namebuf, buf, end - buf);
|
||||
return;
|
||||
}
|
||||
if (round4(len) != len) {
|
||||
ESP_LOGW(TAG, "Bad length %zu", len);
|
||||
return;
|
||||
}
|
||||
hlen = round4(hlen + 3);
|
||||
buf = start_ptr + hlen;
|
||||
if (buf == end) {
|
||||
ESP_LOGV(TAG, "No data after header");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->providers_.count(namebuf) == 0) {
|
||||
ESP_LOGVV(TAG, "Unknown hostname %s", namebuf);
|
||||
return;
|
||||
}
|
||||
auto &provider = this->providers_[namebuf];
|
||||
// if encryption not used with this host, ping check is pointless since it would be easily spoofed.
|
||||
if (provider.encryption_key.empty())
|
||||
ping_key_seen = true;
|
||||
|
||||
ESP_LOGV(TAG, "Found hostname %s", namebuf);
|
||||
#ifdef USE_SENSOR
|
||||
auto &sensors = this->remote_sensors_[namebuf];
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
auto &binary_sensors = this->remote_binary_sensors_[namebuf];
|
||||
#endif
|
||||
|
||||
if (!provider.encryption_key.empty()) {
|
||||
xxtea::decrypt((uint32_t *) buf, (end - buf) / 4, (uint32_t *) provider.encryption_key.data());
|
||||
}
|
||||
byte = *buf++;
|
||||
if (byte == ROLLING_CODE_KEY) {
|
||||
if (!process_rolling_code(provider, buf, end))
|
||||
return;
|
||||
} else if (byte != DATA_KEY) {
|
||||
ESP_LOGV(TAG, "Expected rolling_key or data_key, got %X", byte);
|
||||
return;
|
||||
}
|
||||
while (buf < end) {
|
||||
byte = *buf++;
|
||||
if (byte == ZERO_FILL_KEY)
|
||||
continue;
|
||||
if (byte == PING_KEY) {
|
||||
if (end - buf < 4) {
|
||||
ESP_LOGV(TAG, "PING_KEY requires 4 more bytes");
|
||||
return;
|
||||
}
|
||||
auto key = get_uint32(buf);
|
||||
if (key == this->ping_key_) {
|
||||
ping_key_seen = true;
|
||||
ESP_LOGV(TAG, "Found good ping key %X", (unsigned) key);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!ping_key_seen) {
|
||||
ESP_LOGW(TAG, "Ping key not seen");
|
||||
this->resend_ping_key_ = true;
|
||||
break;
|
||||
}
|
||||
if (byte == BINARY_SENSOR_KEY) {
|
||||
if (end - buf < 3) {
|
||||
ESP_LOGV(TAG, "Binary sensor key requires at least 3 more bytes");
|
||||
return;
|
||||
}
|
||||
rdata.u32 = *buf++;
|
||||
} else if (byte == SENSOR_KEY) {
|
||||
if (end - buf < 6) {
|
||||
ESP_LOGV(TAG, "Sensor key requires at least 6 more bytes");
|
||||
return;
|
||||
}
|
||||
rdata.u32 = get_uint32(buf);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unknown key byte %X", byte);
|
||||
return;
|
||||
}
|
||||
|
||||
hlen = *buf++;
|
||||
if (end - buf < hlen) {
|
||||
ESP_LOGV(TAG, "Name length of %u not available", hlen);
|
||||
return;
|
||||
}
|
||||
memset(namebuf, 0, sizeof namebuf);
|
||||
memcpy(namebuf, buf, hlen);
|
||||
ESP_LOGV(TAG, "Found sensor key %d, id %s, data %lX", byte, namebuf, (unsigned long) rdata.u32);
|
||||
buf += hlen;
|
||||
#ifdef USE_SENSOR
|
||||
if (byte == SENSOR_KEY && sensors.count(namebuf) != 0)
|
||||
sensors[namebuf]->publish_state(rdata.f32);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (byte == BINARY_SENSOR_KEY && binary_sensors.count(namebuf) != 0)
|
||||
binary_sensors[namebuf]->publish_state(rdata.u32 != 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void UDPComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "UDP:");
|
||||
ESP_LOGCONFIG(TAG, " Port: %u", this->port_);
|
||||
ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(this->is_encrypted_()));
|
||||
ESP_LOGCONFIG(TAG, " Ping-pong: %s", YESNO(this->ping_pong_enable_));
|
||||
ESP_LOGCONFIG(TAG, " Listen Port: %u", this->listen_port_);
|
||||
ESP_LOGCONFIG(TAG, " Broadcast Port: %u", this->broadcast_port_);
|
||||
for (const auto &address : this->addresses_)
|
||||
ESP_LOGCONFIG(TAG, " Address: %s", address.c_str());
|
||||
if (this->listen_address_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Listen address: %s", this->listen_address_.value().str().c_str());
|
||||
}
|
||||
#ifdef USE_SENSOR
|
||||
for (auto sensor : this->sensors_)
|
||||
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto sensor : this->binary_sensors_)
|
||||
ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.id);
|
||||
#endif
|
||||
for (const auto &host : this->providers_) {
|
||||
ESP_LOGCONFIG(TAG, " Remote host: %s", host.first.c_str());
|
||||
ESP_LOGCONFIG(TAG, " Encrypted: %s", YESNO(!host.second.encryption_key.empty()));
|
||||
#ifdef USE_SENSOR
|
||||
for (const auto &sensor : this->remote_sensors_[host.first.c_str()])
|
||||
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.first.c_str());
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (const auto &sensor : this->remote_binary_sensors_[host.first.c_str()])
|
||||
ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.first.c_str());
|
||||
#endif
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Broadcasting: %s", YESNO(this->should_broadcast_));
|
||||
ESP_LOGCONFIG(TAG, " Listening: %s", YESNO(this->should_listen_));
|
||||
}
|
||||
void UDPComponent::increment_code_() {
|
||||
if (this->rolling_code_enable_) {
|
||||
if (++this->rolling_code_[0] == 0) {
|
||||
this->rolling_code_[1]++;
|
||||
this->pref_.save(&this->rolling_code_[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
void UDPComponent::send_packet_(void *data, size_t len) {
|
||||
|
||||
void UDPComponent::send_packet(const uint8_t *data, size_t size) {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
for (const auto &saddr : this->sockaddrs_) {
|
||||
auto result = this->broadcast_socket_->sendto(data, len, 0, &saddr, sizeof(saddr));
|
||||
auto result = this->broadcast_socket_->sendto(data, size, 0, &saddr, sizeof(saddr));
|
||||
if (result < 0)
|
||||
ESP_LOGW(TAG, "sendto() error %d", errno);
|
||||
}
|
||||
@ -578,8 +145,8 @@ void UDPComponent::send_packet_(void *data, size_t len) {
|
||||
#ifdef USE_SOCKET_IMPL_LWIP_TCP
|
||||
auto iface = IPAddress(0, 0, 0, 0);
|
||||
for (const auto &saddr : this->ipaddrs_) {
|
||||
if (this->udp_client_.beginPacketMulticast(saddr, this->port_, iface, 128) != 0) {
|
||||
this->udp_client_.write((const uint8_t *) data, len);
|
||||
if (this->udp_client_.beginPacketMulticast(saddr, this->broadcast_port_, iface, 128) != 0) {
|
||||
this->udp_client_.write(data, size);
|
||||
auto result = this->udp_client_.endPacket();
|
||||
if (result == 0)
|
||||
ESP_LOGW(TAG, "udp.write() error");
|
||||
@ -587,18 +154,7 @@ void UDPComponent::send_packet_(void *data, size_t len) {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void UDPComponent::send_ping_pong_request_() {
|
||||
if (!this->ping_pong_enable_ || !network::is_connected())
|
||||
return;
|
||||
this->ping_key_ = random_uint32();
|
||||
this->ping_header_.clear();
|
||||
add(this->ping_header_, MAGIC_PING);
|
||||
add(this->ping_header_, this->name_);
|
||||
add(this->ping_header_, this->ping_key_);
|
||||
this->send_packet_(this->ping_header_.data(), this->ping_header_.size());
|
||||
this->resend_ping_key_ = false;
|
||||
ESP_LOGV(TAG, "Sent new ping request %08X", (unsigned) this->ping_key_);
|
||||
}
|
||||
} // namespace udp
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
|
@ -1,13 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_NETWORK
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#endif
|
||||
@ -15,116 +10,35 @@
|
||||
#include <WiFiUdp.h>
|
||||
#endif
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace udp {
|
||||
|
||||
struct Provider {
|
||||
std::vector<uint8_t> encryption_key;
|
||||
const char *name;
|
||||
uint32_t last_code[2];
|
||||
};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
struct Sensor {
|
||||
sensor::Sensor *sensor;
|
||||
const char *id;
|
||||
bool updated;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
struct BinarySensor {
|
||||
binary_sensor::BinarySensor *sensor;
|
||||
const char *id;
|
||||
bool updated;
|
||||
};
|
||||
#endif
|
||||
|
||||
class UDPComponent : public PollingComponent {
|
||||
static const size_t MAX_PACKET_SIZE = 508;
|
||||
class UDPComponent : public Component {
|
||||
public:
|
||||
void add_address(const char *addr) { this->addresses_.emplace_back(addr); }
|
||||
void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); }
|
||||
void set_listen_port(uint16_t port) { this->listen_port_ = port; }
|
||||
void set_broadcast_port(uint16_t port) { this->broadcast_port_ = port; }
|
||||
void set_should_broadcast() { this->should_broadcast_ = true; }
|
||||
void set_should_listen() { this->should_listen_ = true; }
|
||||
void add_listener(std::function<void(std::vector<uint8_t> &)> &&listener) {
|
||||
this->packet_listeners_.add(std::move(listener));
|
||||
}
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void add_sensor(const char *id, sensor::Sensor *sensor) {
|
||||
Sensor st{sensor, id, true};
|
||||
this->sensors_.push_back(st);
|
||||
}
|
||||
void add_remote_sensor(const char *hostname, const char *remote_id, sensor::Sensor *sensor) {
|
||||
this->add_provider(hostname);
|
||||
this->remote_sensors_[hostname][remote_id] = sensor;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void add_binary_sensor(const char *id, binary_sensor::BinarySensor *sensor) {
|
||||
BinarySensor st{sensor, id, true};
|
||||
this->binary_sensors_.push_back(st);
|
||||
}
|
||||
|
||||
void add_remote_binary_sensor(const char *hostname, const char *remote_id, binary_sensor::BinarySensor *sensor) {
|
||||
this->add_provider(hostname);
|
||||
this->remote_binary_sensors_[hostname][remote_id] = sensor;
|
||||
}
|
||||
#endif
|
||||
void add_address(const char *addr) { this->addresses_.emplace_back(addr); }
|
||||
#ifdef USE_NETWORK
|
||||
void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); }
|
||||
#endif
|
||||
void set_port(uint16_t port) { this->port_ = port; }
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void add_provider(const char *hostname) {
|
||||
if (this->providers_.count(hostname) == 0) {
|
||||
Provider provider;
|
||||
provider.encryption_key = std::vector<uint8_t>{};
|
||||
provider.last_code[0] = 0;
|
||||
provider.last_code[1] = 0;
|
||||
provider.name = hostname;
|
||||
this->providers_[hostname] = provider;
|
||||
#ifdef USE_SENSOR
|
||||
this->remote_sensors_[hostname] = std::map<std::string, sensor::Sensor *>();
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
this->remote_binary_sensors_[hostname] = std::map<std::string, binary_sensor::BinarySensor *>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void set_encryption_key(std::vector<uint8_t> key) { this->encryption_key_ = std::move(key); }
|
||||
void set_rolling_code_enable(bool enable) { this->rolling_code_enable_ = enable; }
|
||||
void set_ping_pong_enable(bool enable) { this->ping_pong_enable_ = enable; }
|
||||
void set_ping_pong_recycle_time(uint32_t recycle_time) { this->ping_pong_recyle_time_ = recycle_time; }
|
||||
void set_provider_encryption(const char *name, std::vector<uint8_t> key) {
|
||||
this->providers_[name].encryption_key = std::move(key);
|
||||
}
|
||||
void send_packet(const uint8_t *data, size_t size);
|
||||
void send_packet(std::vector<uint8_t> &buf) { this->send_packet(buf.data(), buf.size()); }
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; };
|
||||
|
||||
protected:
|
||||
void send_data_(bool all);
|
||||
void process_(uint8_t *buf, size_t len);
|
||||
void flush_();
|
||||
void add_data_(uint8_t key, const char *id, float data);
|
||||
void add_data_(uint8_t key, const char *id, uint32_t data);
|
||||
void increment_code_();
|
||||
void add_binary_data_(uint8_t key, const char *id, bool data);
|
||||
void init_data_();
|
||||
|
||||
bool updated_{};
|
||||
uint16_t port_{18511};
|
||||
uint32_t ping_key_{};
|
||||
uint32_t rolling_code_[2]{};
|
||||
bool rolling_code_enable_{};
|
||||
bool ping_pong_enable_{};
|
||||
uint32_t ping_pong_recyle_time_{};
|
||||
uint32_t last_key_time_{};
|
||||
bool resend_ping_key_{};
|
||||
bool resend_data_{};
|
||||
bool should_send_{};
|
||||
const char *name_{};
|
||||
uint16_t listen_port_{};
|
||||
uint16_t broadcast_port_{};
|
||||
bool should_broadcast_{};
|
||||
bool should_listen_{};
|
||||
ESPPreferenceObject pref_;
|
||||
CallbackManager<void(std::vector<uint8_t> &)> packet_listeners_{};
|
||||
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
std::unique_ptr<socket::Socket> broadcast_socket_ = nullptr;
|
||||
@ -135,32 +49,11 @@ class UDPComponent : public PollingComponent {
|
||||
std::vector<IPAddress> ipaddrs_{};
|
||||
WiFiUDP udp_client_{};
|
||||
#endif
|
||||
std::vector<uint8_t> encryption_key_{};
|
||||
std::vector<std::string> addresses_{};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
std::vector<Sensor> sensors_{};
|
||||
std::map<std::string, std::map<std::string, sensor::Sensor *>> remote_sensors_{};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::vector<BinarySensor> binary_sensors_{};
|
||||
std::map<std::string, std::map<std::string, binary_sensor::BinarySensor *>> remote_binary_sensors_{};
|
||||
#endif
|
||||
#ifdef USE_NETWORK
|
||||
optional<network::IPAddress> listen_address_{};
|
||||
#endif
|
||||
std::map<std::string, Provider> providers_{};
|
||||
std::vector<uint8_t> ping_header_{};
|
||||
std::vector<uint8_t> header_{};
|
||||
std::vector<uint8_t> data_{};
|
||||
std::map<const char *, uint32_t> ping_keys_{};
|
||||
void add_key_(const char *name, uint32_t key);
|
||||
void send_ping_pong_request_();
|
||||
void send_packet_(void *data, size_t len);
|
||||
void process_ping_request_(const char *name, uint8_t *ptr, size_t len);
|
||||
|
||||
inline bool is_encrypted_() { return !this->encryption_key_.empty(); }
|
||||
};
|
||||
|
||||
} // namespace udp
|
||||
} // namespace esphome
|
||||
#endif
|
||||
|
40
tests/components/packet_transport/common.yaml
Normal file
40
tests/components/packet_transport/common.yaml
Normal file
@ -0,0 +1,40 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
udp:
|
||||
listen_address: 239.0.60.53
|
||||
addresses: ["239.0.60.53"]
|
||||
|
||||
packet_transport:
|
||||
platform: udp
|
||||
update_interval: 5s
|
||||
encryption: "our key goes here"
|
||||
rolling_code_enable: true
|
||||
ping_pong_enable: true
|
||||
binary_sensors:
|
||||
- binary_sensor_id1
|
||||
- id: binary_sensor_id1
|
||||
broadcast_id: other_id
|
||||
sensors:
|
||||
- sensor_id1
|
||||
- id: sensor_id1
|
||||
broadcast_id: other_id
|
||||
providers:
|
||||
- name: some-device-name
|
||||
encryption: "their key goes here"
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: sensor_id1
|
||||
- platform: packet_transport
|
||||
provider: some-device-name
|
||||
id: our_id
|
||||
remote_id: some_sensor_id
|
||||
|
||||
binary_sensor:
|
||||
- platform: packet_transport
|
||||
provider: unencrypted-device
|
||||
id: other_binary_sensor_id
|
||||
- platform: template
|
||||
id: binary_sensor_id1
|
1
tests/components/packet_transport/test.bk72xx-ard.yaml
Normal file
1
tests/components/packet_transport/test.bk72xx-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
1
tests/components/packet_transport/test.esp32-ard.yaml
Normal file
1
tests/components/packet_transport/test.esp32-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
1
tests/components/packet_transport/test.esp32-c3-ard.yaml
Normal file
1
tests/components/packet_transport/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
1
tests/components/packet_transport/test.esp32-c3-idf.yaml
Normal file
1
tests/components/packet_transport/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
1
tests/components/packet_transport/test.esp32-idf.yaml
Normal file
1
tests/components/packet_transport/test.esp32-idf.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
1
tests/components/packet_transport/test.esp8266-ard.yaml
Normal file
1
tests/components/packet_transport/test.esp8266-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
4
tests/components/packet_transport/test.host.yaml
Normal file
4
tests/components/packet_transport/test.host.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
|
||||
wifi: !remove
|
1
tests/components/packet_transport/test.rp2040-ard.yaml
Normal file
1
tests/components/packet_transport/test.rp2040-ard.yaml
Normal file
@ -0,0 +1 @@
|
||||
<<: !include common.yaml
|
@ -3,34 +3,18 @@ wifi:
|
||||
password: password1
|
||||
|
||||
udp:
|
||||
update_interval: 5s
|
||||
encryption: "our key goes here"
|
||||
rolling_code_enable: true
|
||||
ping_pong_enable: true
|
||||
id: my_udp
|
||||
listen_address: 239.0.60.53
|
||||
binary_sensors:
|
||||
- binary_sensor_id1
|
||||
- id: binary_sensor_id1
|
||||
broadcast_id: other_id
|
||||
sensors:
|
||||
- sensor_id1
|
||||
- id: sensor_id1
|
||||
broadcast_id: other_id
|
||||
providers:
|
||||
- name: some-device-name
|
||||
encryption: "their key goes here"
|
||||
addresses: ["239.0.60.53"]
|
||||
on_receive:
|
||||
- logger.log:
|
||||
format: "Received %d bytes"
|
||||
args: [data.size()]
|
||||
- udp.write:
|
||||
id: my_udp
|
||||
data: "hello world"
|
||||
- udp.write:
|
||||
id: my_udp
|
||||
data: !lambda |-
|
||||
return std::vector<uint8_t>{1,3,4,5,6};
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
id: sensor_id1
|
||||
- platform: udp
|
||||
provider: some-device-name
|
||||
id: our_id
|
||||
remote_id: some_sensor_id
|
||||
|
||||
binary_sensor:
|
||||
- platform: udp
|
||||
provider: unencrypted-device
|
||||
id: other_binary_sensor_id
|
||||
- platform: template
|
||||
id: binary_sensor_id1
|
||||
|
Loading…
x
Reference in New Issue
Block a user