Merge branch 'sensor_memory' into integration

This commit is contained in:
J. Nick Koston
2025-06-25 14:26:03 +02:00
32 changed files with 371 additions and 220 deletions

View File

@@ -148,6 +148,7 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi
# Filters
Filter = binary_sensor_ns.class_("Filter")
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
@@ -171,6 +172,19 @@ async def invert_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id)
@register_filter(
"timeout",
TimeoutFilter,
cv.templatable(cv.positive_time_period_milliseconds),
)
async def timeout_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id)
await cg.register_component(var, {})
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_timeout_value(template_))
return var
@register_filter(
"delayed_on_off",
DelayedOnOffFilter,

View File

@@ -25,6 +25,12 @@ void Filter::input(bool value) {
}
}
void TimeoutFilter::input(bool value) {
this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
// we do not de-dup here otherwise changes from invalid to valid state will not be output
this->output(value);
}
optional<bool> DelayedOnOffFilter::new_value(bool value) {
if (value) {
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });

View File

@@ -16,7 +16,7 @@ class Filter {
public:
virtual optional<bool> new_value(bool value) = 0;
void input(bool value);
virtual void input(bool value);
void output(bool value);
@@ -28,6 +28,16 @@ class Filter {
Deduplicator<bool> dedup_;
};
class TimeoutFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value) override { return value; }
void input(bool value) override;
template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; }
protected:
TemplatableValue<uint32_t> timeout_delay_{};
};
class DelayedOnOffFilter : public Filter, public Component {
public:
optional<bool> new_value(bool value) override;

View File

@@ -4,6 +4,7 @@
#include "ble_event_pool.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <esp_bt.h>
@@ -516,13 +517,12 @@ void ESP32BLE::dump_config() {
break;
}
ESP_LOGCONFIG(TAG,
"ESP32 BLE:\n"
" MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n"
"BLE:\n"
" MAC address: %s\n"
" IO Capability: %s",
mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5],
io_capability_s);
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
} else {
ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
}
}

View File

@@ -0,0 +1,5 @@
import esphome.config_validation as cv
CONFIG_SCHEMA = cv.invalid(
"The esp32_hall component has been removed as of ESPHome 2025.7.0. See https://github.com/esphome/esphome/pull/9117 for details."
)

View File

@@ -8,6 +8,8 @@
#include "esphome/components/sensor/sensor.h"
#endif
#include "esphome/core/application.h"
#define highbyte(val) (uint8_t)((val) >> 8)
#define lowbyte(val) (uint8_t)((val) &0xff)
@@ -73,9 +75,9 @@ void LD2410Component::dump_config() {
#endif
this->read_all_info();
ESP_LOGCONFIG(TAG,
" Throttle_ : %ums\n"
" MAC Address : %s\n"
" Firmware Version : %s",
" Throttle: %ums\n"
" MAC address: %s\n"
" Firmware version: %s",
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
}
@@ -153,7 +155,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
/*
Reduce data update rate to prevent home assistant database size grow fast
*/
int32_t current_millis = millis();
int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - last_periodic_millis_ < this->throttle_)
return;
last_periodic_millis_ = current_millis;
@@ -299,21 +301,6 @@ const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X";
const std::string UNKNOWN_MAC("unknown");
const std::string NO_MAC("08:05:04:03:02:01");
std::string format_mac(uint8_t *buffer) {
std::string::size_type mac_size = 256;
std::string mac;
do {
mac.resize(mac_size + 1);
mac_size = std::snprintf(&mac[0], mac.size(), MAC_FMT, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
buffer[15]);
} while (mac_size + 1 > mac.size());
mac.resize(mac_size);
if (mac == NO_MAC) {
return UNKNOWN_MAC;
}
return mac;
}
#ifdef USE_NUMBER
std::function<void(void)> set_number_value(number::Number *n, float value) {
float normalized_value = value * 1.0;
@@ -328,40 +315,40 @@ std::function<void(void)> set_number_value(number::Number *n, float value) {
bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]);
if (len < 10) {
ESP_LOGE(TAG, "Error with last command : incorrect length");
ESP_LOGE(TAG, "Invalid length");
return true;
}
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
ESP_LOGE(TAG, "Error with last command : incorrect Header");
ESP_LOGE(TAG, "Invalid header");
return true;
}
if (buffer[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Error with last command : status != 0x01");
ESP_LOGE(TAG, "Invalid status");
return true;
}
if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) {
ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]);
ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]);
return true;
}
switch (buffer[COMMAND]) {
case lowbyte(CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Handled Enable conf command");
ESP_LOGV(TAG, "Enable conf");
break;
case lowbyte(CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Handled Disabled conf command");
ESP_LOGV(TAG, "Disabled conf");
break;
case lowbyte(CMD_SET_BAUD_RATE):
ESP_LOGV(TAG, "Handled baud rate change command");
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGE(TAG, "Change baud rate component config to %s and reinstall", this->baud_rate_select_->state.c_str());
ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
}
#endif
break;
case lowbyte(CMD_VERSION):
this->version_ = format_version(buffer);
ESP_LOGV(TAG, "FW Version is: %s", const_cast<char *>(this->version_.c_str()));
ESP_LOGV(TAG, "Firmware version: %s", const_cast<char *>(this->version_.c_str()));
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(this->version_);
@@ -371,7 +358,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): {
std::string distance_resolution =
DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11]));
ESP_LOGV(TAG, "Distance resolution is: %s", const_cast<char *>(distance_resolution.c_str()));
ESP_LOGV(TAG, "Distance resolution: %s", const_cast<char *>(distance_resolution.c_str()));
#ifdef USE_SELECT
if (this->distance_resolution_select_ != nullptr &&
this->distance_resolution_select_->state != distance_resolution) {
@@ -383,9 +370,9 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]);
this->light_threshold_ = buffer[11] * 1.0;
this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]);
ESP_LOGV(TAG, "Light function is: %s", const_cast<char *>(this->light_function_.c_str()));
ESP_LOGV(TAG, "Light threshold is: %f", this->light_threshold_);
ESP_LOGV(TAG, "Out pin level is: %s", const_cast<char *>(this->out_pin_level_.c_str()));
ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str()));
ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_);
ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str()));
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) {
this->light_function_select_->publish_state(this->light_function_);
@@ -406,11 +393,11 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
if (len < 20) {
return false;
}
this->mac_ = format_mac(buffer);
ESP_LOGV(TAG, "MAC Address is: %s", const_cast<char *>(this->mac_.c_str()));
this->mac_ = format_mac_address_pretty(&buffer[10]);
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(this->mac_);
this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
}
#endif
#ifdef USE_SWITCH
@@ -420,19 +407,19 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
#endif
break;
case lowbyte(CMD_GATE_SENS):
ESP_LOGV(TAG, "Handled sensitivity command");
ESP_LOGV(TAG, "Sensitivity");
break;
case lowbyte(CMD_BLUETOOTH):
ESP_LOGV(TAG, "Handled bluetooth command");
ESP_LOGV(TAG, "Bluetooth");
break;
case lowbyte(CMD_SET_DISTANCE_RESOLUTION):
ESP_LOGV(TAG, "Handled set distance resolution command");
ESP_LOGV(TAG, "Set distance resolution");
break;
case lowbyte(CMD_SET_LIGHT_CONTROL):
ESP_LOGV(TAG, "Handled set light control command");
ESP_LOGV(TAG, "Set light control");
break;
case lowbyte(CMD_BT_PASSWORD):
ESP_LOGV(TAG, "Handled set bluetooth password command");
ESP_LOGV(TAG, "Set bluetooth password");
break;
case lowbyte(CMD_QUERY): // Query parameters response
{
@@ -532,7 +519,7 @@ void LD2410Component::set_baud_rate(const std::string &state) {
void LD2410Component::set_bluetooth_password(const std::string &password) {
if (password.length() != 6) {
ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str());
ESP_LOGE(TAG, "Password must be exactly 6 chars");
return;
}
this->set_config_mode_(true);
@@ -544,7 +531,7 @@ void LD2410Component::set_bluetooth_password(const std::string &password) {
void LD2410Component::set_engineering_mode(bool enable) {
this->set_config_mode_(true);
last_engineering_mode_change_millis_ = millis();
last_engineering_mode_change_millis_ = App.get_loop_component_start_time();
uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
this->send_command_(cmd, nullptr, 0);
this->set_config_mode_(false);

View File

@@ -1,4 +1,5 @@
#include "ld2420.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
/*
@@ -40,7 +41,7 @@ There are three documented parameters for modes:
00 04 = Energy output mode
This mode outputs detailed signal energy values for each gate and the target distance.
The data format consist of the following.
Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF
Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF
HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
00 00 = debug output mode
@@ -67,10 +68,10 @@ float LD2420Component::get_setup_priority() const { return setup_priority::BUS;
void LD2420Component::dump_config() {
ESP_LOGCONFIG(TAG,
"LD2420:\n"
" Firmware Version : %7s\n"
"LD2420 Number:",
" Firmware version: %7s",
this->ld2420_firmware_ver_);
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Number:");
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
@@ -86,10 +87,10 @@ void LD2420Component::dump_config() {
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
#endif
ESP_LOGCONFIG(TAG, "LD2420 Select:");
ESP_LOGCONFIG(TAG, "Select:");
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
}
}
@@ -102,7 +103,7 @@ uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
return checksum;
}
int LD2420Component::get_firmware_int_(const char *version_string) {
int LD2420Component::get_firmware_int(const char *version_string) {
std::string version_str = version_string;
if (version_str[0] == 'v') {
version_str = version_str.substr(1);
@@ -115,7 +116,7 @@ int LD2420Component::get_firmware_int_(const char *version_string) {
void LD2420Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
return;
}
@@ -127,7 +128,7 @@ void LD2420Component::setup() {
const char *pfw = this->ld2420_firmware_ver_;
std::string fw_str(pfw);
for (auto &listener : listeners_) {
for (auto &listener : this->listeners_) {
listener->on_fw_version(fw_str);
}
@@ -137,11 +138,11 @@ void LD2420Component::setup() {
}
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
} else {
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
@@ -151,18 +152,17 @@ void LD2420Component::setup() {
#endif
this->set_system_mode(this->system_mode_);
this->set_config_mode(false);
ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
}
void LD2420Component::apply_config_action() {
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
ESP_LOGCONFIG(TAG, "No configuration change detected");
ESP_LOGD(TAG, "No configuration change detected");
return;
}
ESP_LOGCONFIG(TAG, "Reconfiguring LD2420");
ESP_LOGD(TAG, "Reconfiguring");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
return;
}
@@ -178,13 +178,12 @@ void LD2420Component::apply_config_action() {
this->set_system_mode(this->system_mode_);
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
this->set_operating_mode(OP_NORMAL_MODE_STRING);
ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
}
void LD2420Component::factory_reset_action() {
ESP_LOGCONFIG(TAG, "Setting factory defaults");
ESP_LOGD(TAG, "Setting factory defaults");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
return;
}
@@ -207,18 +206,16 @@ void LD2420Component::factory_reset_action() {
this->init_gate_config_numbers();
this->refresh_gate_config_numbers();
#endif
ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
}
void LD2420Component::restart_module_action() {
ESP_LOGCONFIG(TAG, "Restarting LD2420 module");
ESP_LOGD(TAG, "Restarting");
this->send_module_restart();
this->set_timeout(250, [this]() {
this->set_config_mode(true);
this->set_system_mode(system_mode_);
this->set_system_mode(this->system_mode_);
this->set_config_mode(false);
});
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
}
void LD2420Component::revert_config_action() {
@@ -226,18 +223,18 @@ void LD2420Component::revert_config_action() {
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
ESP_LOGCONFIG(TAG, "Reverted config number edits.");
ESP_LOGD(TAG, "Reverted config number edits");
}
void LD2420Component::loop() {
// If there is a active send command do not process it here, the send command call will handle it.
if (!get_cmd_active_()) {
if (!available())
if (!this->get_cmd_active_()) {
if (!this->available())
return;
static uint8_t buffer[2048];
static uint8_t rx_data;
while (available()) {
rx_data = read();
while (this->available()) {
rx_data = this->read();
this->readline_(rx_data, buffer, sizeof(buffer));
}
}
@@ -292,7 +289,7 @@ void LD2420Component::report_gate_data() {
void LD2420Component::set_operating_mode(const std::string &state) {
// If unsupported firmware ignore mode select
if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
if (LD2420Component::get_firmware_int(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
// Entering Auto Calibrate we need to clear the privoiuos data collection
this->operating_selector_->publish_state(state);
@@ -365,13 +362,13 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
}
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = millis();
const int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
return;
this->last_periodic_millis = current_millis;
for (auto &listener : this->listeners_) {
listener->on_distance(get_distance_());
listener->on_presence(get_presence_());
listener->on_distance(this->get_distance_());
listener->on_presence(this->get_presence_());
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
}
@@ -392,9 +389,9 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
char outbuf[bufsize]{0};
while (true) {
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
set_presence_(false);
this->set_presence_(false);
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
set_presence_(true);
this->set_presence_(true);
}
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
if (index < bufsize - 1) {
@@ -411,18 +408,18 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
}
outbuf[index] = '\0';
if (index > 1)
set_distance_(strtol(outbuf, &endptr, 10));
this->set_distance_(strtol(outbuf, &endptr, 10));
if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = millis();
const int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
return;
this->last_normal_periodic_millis = current_millis;
for (auto &listener : this->listeners_)
listener->on_distance(get_distance_());
listener->on_distance(this->get_distance_());
for (auto &listener : this->listeners_)
listener->on_presence(get_presence_());
listener->on_presence(this->get_presence_());
}
}
@@ -433,10 +430,10 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
uint8_t data_element = 0;
uint16_t data_pos = 0;
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
ESP_LOGW(TAG, "Reply frame too long");
return;
} else if (this->cmd_reply_.length < 2) {
ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
ESP_LOGW(TAG, "Command frame too short");
return;
}
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
@@ -447,13 +444,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
this->cmd_reply_.ack = true;
switch ((uint16_t) this->cmd_reply_.command) {
case (CMD_ENABLE_CONF):
ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
break;
case (CMD_DISABLE_CONF):
ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
break;
case (CMD_READ_REGISTER):
ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result);
ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result);
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
data_pos = 0x0A;
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
@@ -465,13 +462,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
}
break;
case (CMD_WRITE_REGISTER):
ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
break;
case (CMD_WRITE_ABD_PARAM):
ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
break;
case (CMD_READ_ABD_PARAM):
ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
data_pos = CMD_ABD_DATA_REPLY_START;
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
@@ -483,11 +480,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
}
break;
case (CMD_WRITE_SYS_PARAM):
ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
break;
case (CMD_READ_VERSION):
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
ESP_LOGV(TAG, "Firmware version: %7s %s", this->ld2420_firmware_ver_, result);
break;
default:
break;
@@ -533,7 +530,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
}
while (!this->cmd_reply_.ack) {
while (available()) {
while (this->available()) {
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
}
delay_microseconds_safe(1450);
@@ -548,7 +545,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
if (this->cmd_reply_.ack)
retry = 0;
if (this->cmd_reply_.error > 0)
handle_cmd_error(error);
this->handle_cmd_error(error);
}
return error;
}
@@ -563,7 +560,7 @@ uint8_t LD2420Component::set_config_mode(bool enable) {
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
}
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
return this->send_cmd_from_array(cmd_frame);
}
@@ -576,7 +573,7 @@ void LD2420Component::ld2420_restart() {
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_RESTART;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -588,7 +585,7 @@ void LD2420Component::get_reg_value_(uint16_t reg) {
cmd_frame.data[1] = reg;
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -602,11 +599,11 @@ void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
int LD2420Component::get_gate_threshold_(uint8_t gate) {
uint8_t error;
@@ -619,7 +616,7 @@ int LD2420Component::get_gate_threshold_(uint8_t gate) {
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
@@ -644,7 +641,7 @@ int LD2420Component::get_min_max_distances_timeout_() {
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
@@ -667,9 +664,9 @@ void LD2420Component::set_system_mode(uint16_t mode) {
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
cmd_frame.data_length += sizeof(unknown_parm);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
if (this->send_cmd_from_array(cmd_frame) == 0)
set_mode_(mode);
this->set_mode_(mode);
}
void LD2420Component::get_firmware_version_() {
@@ -679,7 +676,7 @@ void LD2420Component::get_firmware_version_() {
cmd_frame.command = CMD_READ_VERSION;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -712,7 +709,7 @@ void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance,
cmd_frame.data_length += sizeof(timeout);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
@@ -738,7 +735,7 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
sizeof(this->new_config.still_thresh[gate]));
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}

View File

@@ -179,7 +179,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
void set_operating_mode(const std::string &state);
void auto_calibrate_sensitivity();
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
uint8_t calc_checksum(void *data, size_t size);
static uint8_t calc_checksum(void *data, size_t size);
RegConfigT current_config;
RegConfigT new_config;
@@ -222,7 +222,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
volatile bool ack;
};
int get_firmware_int_(const char *version_string);
static int get_firmware_int(const char *version_string);
void get_firmware_version_();
int get_gate_threshold_(uint8_t gate);
void get_reg_value_(uint16_t reg);

View File

@@ -6,7 +6,9 @@
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#define highbyte(val) (uint8_t)((val) >> 8)
#define lowbyte(val) (uint8_t)((val) &0xff)
@@ -96,11 +98,6 @@ static inline std::string get_direction(int16_t speed) {
return STATIONARY;
}
static inline std::string format_mac(uint8_t *buffer) {
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
buffer[15]);
}
static inline std::string format_version(uint8_t *buffer) {
return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
buffer[14]);
@@ -120,7 +117,7 @@ void LD2450Component::setup() {
}
void LD2450Component::dump_config() {
ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:");
ESP_LOGCONFIG(TAG, "LD2450:");
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
@@ -189,9 +186,9 @@ void LD2450Component::dump_config() {
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
#endif
ESP_LOGCONFIG(TAG,
" Throttle : %ums\n"
" MAC Address : %s\n"
" Firmware version : %s",
" Throttle: %ums\n"
" MAC Address: %s\n"
" Firmware version: %s",
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
}
@@ -266,8 +263,7 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
if (this->timeout_ == 0) {
this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
}
auto current_millis = millis();
return current_millis - check_millis >= this->timeout_;
return App.get_loop_component_start_time() - check_millis >= this->timeout_;
}
// Extract, store and publish zone details LD2450 buffer
@@ -354,25 +350,24 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
// Header Target 1 Target 2 Target 3 End
void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
ESP_LOGE(TAG, "Periodic data: invalid message length");
ESP_LOGE(TAG, "Invalid message length");
return;
}
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
ESP_LOGE(TAG, "Periodic data: invalid message header");
ESP_LOGE(TAG, "Invalid message header");
return;
}
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
ESP_LOGE(TAG, "Periodic data: invalid message footer");
ESP_LOGE(TAG, "Invalid message footer");
return;
}
auto current_millis = millis();
if (current_millis - this->last_periodic_millis_ < this->throttle_) {
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
return;
}
this->last_periodic_millis_ = current_millis;
this->last_periodic_millis_ = App.get_loop_component_start_time();
int16_t target_count = 0;
int16_t still_target_count = 0;
@@ -555,13 +550,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#ifdef USE_SENSOR
// For presence timeout check
if (target_count > 0) {
this->presence_millis_ = millis();
this->presence_millis_ = App.get_loop_component_start_time();
}
if (moving_target_count > 0) {
this->moving_presence_millis_ = millis();
this->moving_presence_millis_ = App.get_loop_component_start_time();
}
if (still_target_count > 0) {
this->still_presence_millis_ = millis();
this->still_presence_millis_ = App.get_loop_component_start_time();
}
#endif
}
@@ -569,31 +564,31 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]);
if (len < 10) {
ESP_LOGE(TAG, "Ack data: invalid length");
ESP_LOGE(TAG, "Invalid ack length");
return true;
}
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]);
ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]);
return true;
}
if (buffer[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Ack data: invalid status");
ESP_LOGE(TAG, "Invalid ack status");
return true;
}
if (buffer[8] || buffer[9]) {
ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]);
ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]);
return true;
}
switch (buffer[COMMAND]) {
case lowbyte(CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Got enable conf command");
ESP_LOGV(TAG, "Enable conf command");
break;
case lowbyte(CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Got disable conf command");
ESP_LOGV(TAG, "Disable conf command");
break;
case lowbyte(CMD_SET_BAUD_RATE):
ESP_LOGV(TAG, "Got baud rate change command");
ESP_LOGV(TAG, "Baud rate change command");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
@@ -613,7 +608,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
if (len < 20) {
return false;
}
this->mac_ = ld2450::format_mac(buffer);
this->mac_ = format_mac_address_pretty(&buffer[10]);
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
@@ -622,15 +617,15 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
#ifdef USE_SWITCH
if (this->bluetooth_switch_ != nullptr) {
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
}
#endif
break;
case lowbyte(CMD_BLUETOOTH):
ESP_LOGV(TAG, "Got Bluetooth command");
ESP_LOGV(TAG, "Bluetooth command");
break;
case lowbyte(CMD_SINGLE_TARGET_MODE):
ESP_LOGV(TAG, "Got single target conf command");
ESP_LOGV(TAG, "Single target conf command");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(false);
@@ -638,7 +633,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_MULTI_TARGET_MODE):
ESP_LOGV(TAG, "Got multi target conf command");
ESP_LOGV(TAG, "Multi target conf command");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(true);
@@ -646,7 +641,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_QUERY_TARGET_MODE):
ESP_LOGV(TAG, "Got query target tracking mode command");
ESP_LOGV(TAG, "Query target tracking mode command");
#ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(buffer[10] == 0x02);
@@ -654,7 +649,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
#endif
break;
case lowbyte(CMD_QUERY_ZONE):
ESP_LOGV(TAG, "Got query zone conf command");
ESP_LOGV(TAG, "Query zone conf command");
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
this->publish_zone_type();
#ifdef USE_SELECT
@@ -674,7 +669,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
this->process_zone_(buffer);
break;
case lowbyte(CMD_SET_ZONE):
ESP_LOGV(TAG, "Got set zone conf command");
ESP_LOGV(TAG, "Set zone conf command");
this->query_zone_info();
break;
default:

View File

@@ -64,6 +64,14 @@ class ModbusDevice {
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
}
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
void send_error(uint8_t function_code, uint8_t exception_code) {
std::vector<uint8_t> error_response;
error_response.reserve(3);
error_response.push_back(this->address_);
error_response.push_back(function_code | 0x80);
error_response.push_back(exception_code);
this->send_raw(error_response);
}
// If more than one device is connected block sending a new command before a response is received
bool waiting_for_response() { return parent_->waiting_for_response != 0; }

View File

@@ -112,6 +112,22 @@ TYPE_REGISTER_MAP = {
"FP32_R": 2,
}
CPP_TYPE_REGISTER_MAP = {
"RAW": cg.uint16,
"U_WORD": cg.uint16,
"S_WORD": cg.int16,
"U_DWORD": cg.uint32,
"U_DWORD_R": cg.uint32,
"S_DWORD": cg.int32,
"S_DWORD_R": cg.int32,
"U_QWORD": cg.uint64,
"U_QWORD_R": cg.uint64,
"S_QWORD": cg.int64,
"S_QWORD_R": cg.int64,
"FP32": cg.float_,
"FP32_R": cg.float_,
}
ModbusCommandSentTrigger = modbus_controller_ns.class_(
"ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_)
)
@@ -285,21 +301,24 @@ async def to_code(config):
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
if CONF_SERVER_REGISTERS in config:
for server_register in config[CONF_SERVER_REGISTERS]:
server_register_var = cg.new_Pvariable(
server_register[CONF_ID],
server_register[CONF_ADDRESS],
server_register[CONF_VALUE_TYPE],
TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]],
)
cpp_type = CPP_TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]]
cg.add(
var.add_server_register(
cg.new_Pvariable(
server_register[CONF_ID],
server_register[CONF_ADDRESS],
server_register[CONF_VALUE_TYPE],
TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]],
await cg.process_lambda(
server_register[CONF_READ_LAMBDA],
[],
return_type=cg.float_,
),
)
server_register_var.set_read_lambda(
cg.TemplateArguments(cpp_type),
await cg.process_lambda(
server_register[CONF_READ_LAMBDA],
[(cg.uint16, "address")],
return_type=cpp_type,
),
)
)
cg.add(var.add_server_register(server_register_var))
await register_modbus_device(var, config)
for conf in config.get(CONF_ON_COMMAND_SENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -117,12 +117,17 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
bool found = false;
for (auto *server_register : this->server_registers_) {
if (server_register->address == current_address) {
float value = server_register->read_lambda();
if (!server_register->read_lambda) {
break;
}
int64_t value = server_register->read_lambda();
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
server_register->address, static_cast<size_t>(server_register->value_type),
server_register->register_count, server_register->format_value(value).c_str());
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.",
server_register->address, static_cast<uint8_t>(server_register->value_type),
server_register->register_count, value);
std::vector<uint16_t> payload = float_to_payload(value, server_register->value_type);
std::vector<uint16_t> payload;
payload.reserve(server_register->register_count * 2);
number_to_payload(payload, value, server_register->value_type);
sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
current_address += server_register->register_count;
found = true;
@@ -132,11 +137,7 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
if (!found) {
ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
std::vector<uint8_t> error_response;
error_response.push_back(this->address_);
error_response.push_back(0x81);
error_response.push_back(0x02);
this->send_raw(error_response);
send_error(function_code, 0x02);
return;
}
}

View File

@@ -63,6 +63,10 @@ enum class SensorValueType : uint8_t {
FP32_R = 0xD
};
inline bool value_type_is_float(SensorValueType v) {
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
}
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
@@ -253,18 +257,53 @@ class SensorItem {
};
class ServerRegister {
using ReadLambda = std::function<int64_t()>;
public:
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count,
std::function<float()> read_lambda) {
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) {
this->address = address;
this->value_type = value_type;
this->register_count = register_count;
this->read_lambda = std::move(read_lambda);
}
template<typename T> void set_read_lambda(const std::function<T(uint16_t address)> &&user_read_lambda) {
this->read_lambda = [this, user_read_lambda]() -> int64_t {
T user_value = user_read_lambda(this->address);
if constexpr (std::is_same_v<T, float>) {
return bit_cast<uint32_t>(user_value);
} else {
return static_cast<int64_t>(user_value);
}
};
}
// Formats a raw value into a string representation based on the value type for debugging
std::string format_value(int64_t value) const {
switch (this->value_type) {
case SensorValueType::U_WORD:
case SensorValueType::U_DWORD:
case SensorValueType::U_DWORD_R:
case SensorValueType::U_QWORD:
case SensorValueType::U_QWORD_R:
return std::to_string(static_cast<uint64_t>(value));
case SensorValueType::S_WORD:
case SensorValueType::S_DWORD:
case SensorValueType::S_DWORD_R:
case SensorValueType::S_QWORD:
case SensorValueType::S_QWORD_R:
return std::to_string(value);
case SensorValueType::FP32_R:
case SensorValueType::FP32:
return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
default:
return std::to_string(value);
}
}
uint16_t address{0};
SensorValueType value_type{SensorValueType::RAW};
uint8_t register_count{0};
std::function<float()> read_lambda;
ReadLambda read_lambda;
};
// ModbusController::create_register_ranges_ tries to optimize register range
@@ -444,7 +483,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
void on_modbus_data(const std::vector<uint8_t> &data) override;
/// called when a modbus error response was received
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
/// called when a modbus request (function code 3 or 4) was parsed without errors
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
@@ -529,7 +568,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
float float_value;
if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) {
if (value_type_is_float(item.sensor_value_type)) {
float_value = bit_cast<float>(static_cast<uint32_t>(number));
} else {
float_value = static_cast<float>(number);
@@ -541,7 +580,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
int64_t val;
if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) {
if (value_type_is_float(value_type)) {
val = bit_cast<uint32_t>(value);
} else {
val = llroundf(value);

View File

@@ -17,7 +17,8 @@ enum class MQTTClientDisconnectReason : int8_t {
MQTT_MALFORMED_CREDENTIALS = 4,
MQTT_NOT_AUTHORIZED = 5,
ESP8266_NOT_ENOUGH_SPACE = 6,
TLS_BAD_FINGERPRINT = 7
TLS_BAD_FINGERPRINT = 7,
DNS_RESOLVE_ERROR = 8
};
/// internal struct for MQTT messages.

View File

@@ -229,6 +229,8 @@ void MQTTClientComponent::check_dnslookup_() {
if (this->dns_resolve_error_) {
ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str());
this->state_ = MQTT_CLIENT_DISCONNECTED;
this->disconnect_reason_ = MQTTClientDisconnectReason::DNS_RESOLVE_ERROR;
this->on_disconnect_.call(MQTTClientDisconnectReason::DNS_RESOLVE_ERROR);
return;
}
@@ -698,7 +700,9 @@ void MQTTClientComponent::set_on_connect(mqtt_on_connect_callback_t &&callback)
}
void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) {
auto callback_copy = callback;
this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback));
this->on_disconnect_.add(std::move(callback_copy));
}
#if ASYNC_TCP_SSL_ENABLED

View File

@@ -8,6 +8,7 @@
#include "esphome/components/network/ip_address.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#if defined(USE_ESP32)
#include "mqtt_backend_esp32.h"
@@ -334,6 +335,7 @@ class MQTTClientComponent : public Component {
uint32_t connect_begin_;
uint32_t last_connected_{0};
optional<MQTTClientDisconnectReason> disconnect_reason_{};
CallbackManager<MQTTBackend::on_disconnect_callback_t> on_disconnect_;
bool publish_nan_as_none_{false};
bool wait_for_connection_{false};

View File

@@ -23,16 +23,22 @@ std::string state_class_to_string(StateClass state_class) {
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
int8_t Sensor::get_accuracy_decimals() {
if (this->accuracy_decimals_.has_value())
return *this->accuracy_decimals_;
if (this->sensor_flags_.has_accuracy_override)
return this->accuracy_decimals_;
return 0;
}
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) {
this->accuracy_decimals_ = accuracy_decimals;
this->sensor_flags_.has_accuracy_override = true;
}
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
void Sensor::set_state_class(StateClass state_class) {
this->state_class_ = state_class;
this->sensor_flags_.has_state_class_override = true;
}
StateClass Sensor::get_state_class() {
if (this->state_class_.has_value())
return *this->state_class_;
if (this->sensor_flags_.has_state_class_override)
return this->state_class_;
return StateClass::STATE_CLASS_NONE;
}

View File

@@ -80,9 +80,9 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
* state changes to the database when they are published, even if the state is the
* same as before.
*/
bool get_force_update() const { return force_update_; }
bool get_force_update() const { return sensor_flags_.force_update; }
/// Set force update mode.
void set_force_update(bool force_update) { force_update_ = force_update; }
void set_force_update(bool force_update) { sensor_flags_.force_update = force_update; }
/// Add a filter to the filter chain. Will be appended to the back.
void add_filter(Filter *filter);
@@ -155,9 +155,17 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
Filter *filter_list_{nullptr}; ///< Store all active filters.
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
bool force_update_{false}; ///< Force update mode
// Group small members together to avoid padding
int8_t accuracy_decimals_{-1}; ///< Accuracy in decimals (-1 = not set)
StateClass state_class_{STATE_CLASS_NONE}; ///< State class (STATE_CLASS_NONE = not set)
// Bit-packed flags for sensor-specific settings
struct SensorFlags {
uint8_t has_accuracy_override : 1;
uint8_t has_state_class_override : 1;
uint8_t force_update : 1;
uint8_t reserved : 5; // Reserved for future use
} sensor_flags_{};
};
} // namespace sensor

View File

@@ -741,11 +741,6 @@ void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->po
void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; }
std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
char buf[20];
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return buf;
}
bool WiFiComponent::is_captive_portal_active_() {
#ifdef USE_CAPTIVE_PORTAL
return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active();

View File

@@ -321,8 +321,6 @@ class WiFiComponent : public Component {
int32_t get_wifi_channel();
protected:
static std::string format_mac_addr(const uint8_t mac[6]);
#ifdef USE_WIFI_AP
void setup_ap_config_();
#endif // USE_WIFI_AP

View File

@@ -550,7 +550,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
#if USE_NETWORK_IPV6
this->set_timeout(100, [] { WiFi.enableIPv6(); });
#endif /* USE_NETWORK_IPV6 */
@@ -566,7 +566,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
}
uint8_t reason = it.reason;
@@ -636,13 +636,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
auto it = info.wifi_sta_connected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
auto it = info.wifi_sta_disconnected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
@@ -651,7 +651,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
auto it = info.wifi_ap_probereqrecved;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
break;
}
default:

View File

@@ -496,7 +496,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
char buf[33];
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel);
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
it.channel);
s_sta_connected = true;
break;
}
@@ -510,7 +511,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
s_sta_connect_not_found = true;
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
s_sta_connect_error = true;
}
s_sta_connected = false;
@@ -545,17 +546,17 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
}
case EVENT_SOFTAPMODE_STACONNECTED: {
auto it = event->event_info.sta_connected;
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
break;
}
case EVENT_SOFTAPMODE_STADISCONNECTED: {
auto it = event->event_info.sta_disconnected;
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
break;
}
case EVENT_SOFTAPMODE_PROBEREQRECVED: {
auto it = event->event_info.ap_probereqrecved;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
break;
}
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
@@ -567,7 +568,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
}
case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
auto it = event->event_info.distribute_sta_ip;
ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_addr(it.mac).c_str(),
ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(),
format_ip_addr(it.ip).c_str(), it.aid);
break;
}

View File

@@ -691,7 +691,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
s_sta_connected = true;
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
@@ -708,7 +708,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
return;
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
s_sta_connect_error = true;
}
s_sta_connected = false;
@@ -780,15 +780,15 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) {
const auto &it = data->data.ap_probe_req_rx;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) {
const auto &it = data->data.ap_staconnected;
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(it.mac).c_str());
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str());
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) {
const auto &it = data->data.ap_stadisconnected;
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str());
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str());
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) {
const auto &it = data->data.ip_ap_staipassigned;

View File

@@ -281,7 +281,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
memcpy(buf, it.ssid, it.ssid_len);
buf[it.ssid_len] = '\0';
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
break;
}
@@ -294,7 +294,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
} else {
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
}
uint8_t reason = it.reason;
@@ -349,13 +349,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
auto it = info.wifi_sta_connected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
auto it = info.wifi_sta_disconnected;
auto &mac = it.bssid;
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
@@ -364,7 +364,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
auto it = info.wifi_ap_probereqrecved;
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
break;
}
default:

View File

@@ -320,7 +320,7 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
memcpy(mac_address + 4, mac_reverse + 1, 1);
memcpy(mac_address + 5, mac_reverse, 1);
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str());
ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str());
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str());
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());

View File

@@ -1099,7 +1099,7 @@ UNIT_KILOMETER_PER_HOUR = "km/h"
UNIT_KILOVOLT_AMPS = "kVA"
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR"
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kvarh"
UNIT_KILOWATT = "kW"
UNIT_KILOWATT_HOURS = "kWh"
UNIT_LITRE = "L"
@@ -1135,7 +1135,7 @@ UNIT_VOLT = "V"
UNIT_VOLT_AMPS = "VA"
UNIT_VOLT_AMPS_HOURS = "VAh"
UNIT_VOLT_AMPS_REACTIVE = "var"
UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh"
UNIT_VOLT_AMPS_REACTIVE_HOURS = "varh"
UNIT_WATT = "W"
UNIT_WATT_HOURS = "Wh"

View File

@@ -356,6 +356,10 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
return chars;
}
std::string format_mac_address_pretty(const uint8_t *mac) {
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
std::string format_hex(const uint8_t *data, size_t length) {
std::string ret;
@@ -732,7 +736,7 @@ std::string get_mac_address() {
std::string get_mac_address_pretty() {
uint8_t mac[6];
get_mac_address_raw(mac);
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return format_mac_address_pretty(mac);
}
#ifdef USE_ESP32

View File

@@ -402,6 +402,8 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<
return parse_hex<T>(str.c_str(), str.length());
}
/// Format the six-byte array \p mac into a MAC address.
std::string format_mac_address_pretty(const uint8_t mac[6]);
/// Format the byte array \p data of length \p len in lowercased hex.
std::string format_hex(const uint8_t *data, size_t length);
/// Format the vector \p data in lowercased hex.

View File

@@ -4,6 +4,31 @@ binary_sensor:
id: some_binary_sensor
name: "Random binary"
lambda: return (random_uint32() & 1) == 0;
filters:
- invert:
- delayed_on: 100ms
- delayed_off: 100ms
# Templated, delays for 1s (1000ms) only if a reed switch is active
- delayed_on_off: !lambda "return 1000;"
- delayed_on_off:
time_on: 10s
time_off: !lambda "return 1000;"
- autorepeat:
- delay: 1s
time_off: 100ms
time_on: 900ms
- delay: 5s
time_off: 100ms
time_on: 400ms
- lambda: |-
if (id(some_binary_sensor).state) {
return x;
} else {
return {};
}
- settle: 100ms
- timeout: 10s
on_state_change:
then:
- logger.log:

View File

@@ -8,5 +8,8 @@ sensor:
name: Test Sensor
id: test_sensor
unit_of_measurement: °C
accuracy_decimals: 2
state_class: measurement
force_update: true
lambda: return 42.0;
update_interval: 0.1s

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
import aioesphomeapi
from aioesphomeapi import EntityState
import pytest
@@ -47,3 +48,23 @@ async def test_host_mode_with_sensor(
# Verify the sensor state
assert test_sensor_state.state == 42.0
assert len(states) > 0, "No states received"
# Verify the optimized fields are working correctly
# Get entity info to check accuracy_decimals, state_class, etc.
entities, _ = await client.list_entities_services()
sensor_info: aioesphomeapi.SensorInfo | None = None
for entity in entities:
if isinstance(entity, aioesphomeapi.SensorInfo):
sensor_info = entity
break
assert sensor_info is not None, "Sensor entity info not found"
assert sensor_info.accuracy_decimals == 2, (
f"Expected accuracy_decimals=2, got {sensor_info.accuracy_decimals}"
)
assert sensor_info.state_class == aioesphomeapi.StateClass.MEASUREMENT, (
f"Expected state_class=StateClass.MEASUREMENT, got {sensor_info.state_class}"
)
assert sensor_info.force_update is True, (
f"Expected force_update=True, got {sensor_info.force_update}"
)