mirror of
https://github.com/esphome/esphome.git
synced 2025-09-02 19:32:19 +01:00
Merge remote-tracking branch 'upstream/dev' into zwave_proxy
This commit is contained in:
@@ -1 +1 @@
|
||||
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273
|
||||
0440e35cf89a49e8a35fd3690ed453a72b7b6f61b9d346ced6140e1c0d39dff6
|
||||
|
30
.github/workflows/auto-label-pr.yml
vendored
30
.github/workflows/auto-label-pr.yml
vendored
@@ -105,7 +105,9 @@ jobs:
|
||||
|
||||
// Calculate data from PR files
|
||||
const changedFiles = prFiles.map(file => file.filename);
|
||||
const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
const totalChanges = totalAdditions + totalDeletions;
|
||||
|
||||
console.log('Current labels:', currentLabels.join(', '));
|
||||
console.log('Changed files:', changedFiles.length);
|
||||
@@ -231,16 +233,21 @@ jobs:
|
||||
// Strategy: PR size detection
|
||||
async function detectPRSize() {
|
||||
const labels = new Set();
|
||||
const testChanges = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||
|
||||
const nonTestChanges = totalChanges - testChanges;
|
||||
|
||||
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||
labels.add('small-pr');
|
||||
return labels;
|
||||
}
|
||||
|
||||
const testAdditions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
const testDeletions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
|
||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||
|
||||
// Don't add too-big if mega-pr label is already present
|
||||
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
||||
labels.add('too-big');
|
||||
@@ -375,7 +382,7 @@ jobs:
|
||||
const labels = new Set();
|
||||
|
||||
// Check for missing tests
|
||||
if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) {
|
||||
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
|
||||
labels.add('needs-tests');
|
||||
}
|
||||
|
||||
@@ -412,10 +419,13 @@ jobs:
|
||||
|
||||
// Too big message
|
||||
if (finalLabels.includes('too-big')) {
|
||||
const testChanges = prFiles
|
||||
const testAdditions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||
const nonTestChanges = totalChanges - testChanges;
|
||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||
const testDeletions = prFiles
|
||||
.filter(file => file.filename.startsWith('tests/'))
|
||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||
|
||||
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
||||
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -156,7 +156,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.4.3
|
||||
uses: codecov/codecov-action@v5.5.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
|
@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
|
||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
|
||||
uint8_t rev_newaddress = ~newaddress;
|
||||
std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
|
||||
data[4] = calc_crc8_(data, 4);
|
||||
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
||||
if (!this->write_bytes(REG_ADDRESS, data)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->status_set_warning();
|
||||
@@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set
|
||||
|
||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
|
||||
std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
|
||||
data[4] = calc_crc8_(data, 4);
|
||||
data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
|
||||
if (!this->write_bytes(REG_CALIBRATION, data)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->status_set_warning();
|
||||
@@ -184,7 +184,7 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
||||
auto res = *data;
|
||||
auto crc_byte = res[len];
|
||||
|
||||
if (crc_byte != calc_crc8_(res, len)) {
|
||||
if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
|
||||
return optional<std::array<uint8_t, N>>();
|
||||
@@ -192,20 +192,5 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
|
||||
uint8_t i, byte1, crc = 0xFF;
|
||||
for (byte1 = 0; byte1 < num; byte1++) {
|
||||
crc ^= (dat[byte1]);
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x31;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
} // namespace ags10
|
||||
} // namespace esphome
|
||||
|
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ags10 {
|
||||
@@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
* Read, checks and returns data from the sensor.
|
||||
*/
|
||||
template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
|
||||
|
||||
/**
|
||||
* Calculates CRC8 value.
|
||||
*
|
||||
* CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
|
||||
*
|
||||
* @param[in] dat the data buffer
|
||||
* @param num number of bytes in the buffer
|
||||
*/
|
||||
template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
|
||||
};
|
||||
|
||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
|
||||
|
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
@@ -29,22 +29,6 @@ namespace am2315c {
|
||||
|
||||
static const char *const TAG = "am2315c";
|
||||
|
||||
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
|
||||
uint8_t crc = 0xFF;
|
||||
while (len--) {
|
||||
crc ^= *data++;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (crc & 0x80) {
|
||||
crc <<= 1;
|
||||
crc ^= 0x31;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool AM2315C::reset_register_(uint8_t reg) {
|
||||
// code based on demo code sent by www.aosong.com
|
||||
// no further documentation.
|
||||
@@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
||||
humidity = raw * 9.5367431640625e-5;
|
||||
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
|
||||
temperature = raw * 1.9073486328125e-4 - 50;
|
||||
return this->crc8_(data, 6) == data[6];
|
||||
return crc8(data, 6, 0xFF, 0x31, true) == data[6];
|
||||
}
|
||||
|
||||
void AM2315C::setup() {
|
||||
|
@@ -21,9 +21,9 @@
|
||||
// SOFTWARE.
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace am2315c {
|
||||
@@ -39,7 +39,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||
|
||||
protected:
|
||||
uint8_t crc8_(uint8_t *data, uint8_t len);
|
||||
bool convert_(uint8_t *data, float &humidity, float &temperature);
|
||||
bool reset_register_(uint8_t reg);
|
||||
|
||||
|
@@ -321,6 +321,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
|
||||
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
|
||||
)
|
||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
|
||||
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||
serv = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||
cg.add(var.set_service("esphome.tag_scanned"))
|
||||
|
@@ -468,9 +468,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
||||
resp.cold_white = values.get_cold_white();
|
||||
resp.warm_white = values.get_warm_white();
|
||||
if (light->supports_effects()) {
|
||||
// get_effect_name() returns temporary std::string - must store it
|
||||
std::string effect_name = light->get_effect_name();
|
||||
resp.set_effect(StringRef(effect_name));
|
||||
resp.set_effect(light->get_effect_name_ref());
|
||||
}
|
||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -1434,9 +1432,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||
|
||||
// get_compilation_time() returns temporary std::string - must store it
|
||||
std::string compilation_time = App.get_compilation_time();
|
||||
resp.set_compilation_time(StringRef(compilation_time));
|
||||
resp.set_compilation_time(App.get_compilation_time_ref());
|
||||
|
||||
// Compile-time StringRef constants for manufacturers
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
|
@@ -306,9 +306,15 @@ class APIConnection final : public APIServerConnection {
|
||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
// IMPORTANT: get_object_id() may return a temporary std::string
|
||||
// Try to use static reference first to avoid allocation
|
||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
||||
if (!static_ref.empty()) {
|
||||
msg.set_object_id(static_ref);
|
||||
} else {
|
||||
// Dynamic case - need to allocate
|
||||
std::string object_id = entity->get_object_id();
|
||||
msg.set_object_id(StringRef(object_id));
|
||||
}
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.set_name(entity->get_name());
|
||||
|
@@ -7,6 +7,19 @@ namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "binary_sensor";
|
||||
|
||||
// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
|
||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
|
||||
if (obj == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||
|
||||
if (!obj->get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void BinarySensor::publish_state(bool new_state) {
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(new_state);
|
||||
|
@@ -10,13 +10,10 @@ namespace esphome {
|
||||
|
||||
namespace binary_sensor {
|
||||
|
||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
}
|
||||
class BinarySensor;
|
||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
|
||||
|
||||
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||
|
||||
#define SUB_BINARY_SENSOR(name) \
|
||||
protected: \
|
||||
|
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
@@ -63,6 +63,6 @@ def to_code(config):
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||
yield output.register_output(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
yield cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
|
||||
|
||||
class BluetoothProxy;
|
||||
|
||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
@@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
|
||||
SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
|
||||
};
|
||||
|
||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
|
||||
friend class BluetoothConnection; // Allow connection to update connections_free_response_
|
||||
public:
|
||||
BluetoothProxy();
|
||||
|
@@ -6,6 +6,19 @@ namespace button {
|
||||
|
||||
static const char *const TAG = "button";
|
||||
|
||||
// Function implementation of LOG_BUTTON macro to reduce code size
|
||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj) {
|
||||
if (obj == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||
|
||||
if (!obj->get_icon().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Button::press() {
|
||||
ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
|
||||
this->press_action();
|
||||
|
@@ -7,13 +7,10 @@
|
||||
namespace esphome {
|
||||
namespace button {
|
||||
|
||||
#define LOG_BUTTON(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
}
|
||||
class Button;
|
||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
|
||||
|
||||
#define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||
|
||||
#define SUB_BUTTON(name) \
|
||||
protected: \
|
||||
|
@@ -153,8 +153,8 @@ void CCS811Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CCS811");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
|
||||
LOG_SENSOR(" ", "CO2 Sensor", this->co2_);
|
||||
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_);
|
||||
LOG_TEXT_SENSOR(" ", "Firmware Version Sensor", this->version_)
|
||||
if (this->baseline_) {
|
||||
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
|
||||
|
@@ -228,9 +228,9 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
|
||||
|
||||
|
||||
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
|
||||
def cover_toggle_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
async def cover_toggle_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
|
@@ -159,8 +159,7 @@ void BLEClientBase::disconnect() {
|
||||
return;
|
||||
}
|
||||
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->log_warning_("Disconnect before connected, disconnect scheduled.");
|
||||
this->want_disconnect_ = true;
|
||||
return;
|
||||
}
|
||||
@@ -172,13 +171,11 @@ void BLEClientBase::unconditional_disconnect() {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
|
||||
this->conn_id_);
|
||||
if (this->state_ == espbt::ClientState::DISCONNECTING) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->log_error_("Already disconnecting");
|
||||
return;
|
||||
}
|
||||
if (this->conn_id_ == UNSET_CONN_ID) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->log_error_("conn id unset, cannot disconnect");
|
||||
return;
|
||||
}
|
||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||
@@ -234,6 +231,18 @@ void BLEClientBase::log_connection_params_(const char *param_type) {
|
||||
ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_error_(const char *message) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_error_(const char *message, int code) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code);
|
||||
}
|
||||
|
||||
void BLEClientBase::log_warning_(const char *message) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
|
||||
}
|
||||
|
||||
void BLEClientBase::restore_medium_conn_params_() {
|
||||
// Restore to medium connection parameters after initial connection phase
|
||||
// This balances performance with bandwidth usage for normal operation
|
||||
@@ -264,8 +273,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->app_id);
|
||||
this->gattc_if_ = esp_gattc_if;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->reg.app_id, param->reg.status);
|
||||
this->log_error_("gattc app registration failed status", param->reg.status);
|
||||
this->status_ = param->reg.status;
|
||||
this->mark_failed();
|
||||
}
|
||||
@@ -281,8 +289,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
// This should not happen but lets log it in case it does
|
||||
// because it means we have a bad assumption about how the
|
||||
// ESP BT stack works.
|
||||
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while in %s state, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
|
||||
this->log_error_("ESP_GATTC_OPEN_EVT wrong state status", param->open.status);
|
||||
}
|
||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||
this->log_gattc_warning_("Connection open", param->open.status);
|
||||
@@ -307,7 +314,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str());
|
||||
this->log_event_("Searching for services");
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||
break;
|
||||
}
|
||||
@@ -332,8 +339,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
// Check if we were disconnected while waiting for service discovery
|
||||
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
|
||||
this->state_ == espbt::ClientState::CONNECTED) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
this->log_warning_("Remote closed during discovery");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
|
||||
this->address_str_.c_str(), param->disconnect.reason);
|
||||
@@ -506,16 +512,14 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
|
||||
return;
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
|
||||
ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_.c_str(),
|
||||
format_hex(bd_addr, 6).c_str());
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
|
||||
param->ble_security.auth_cmpl.fail_reason);
|
||||
this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
this->paired_ = true;
|
||||
ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
|
||||
param->ble_security.auth_cmpl.auth_mode);
|
||||
ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_.c_str(),
|
||||
param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@@ -137,6 +137,10 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
|
||||
void log_gattc_warning_(const char *operation, esp_err_t err);
|
||||
void log_connection_params_(const char *param_type);
|
||||
// Compact error logging helpers to reduce flash usage
|
||||
void log_error_(const char *message);
|
||||
void log_error_(const char *message, int code);
|
||||
void log_warning_(const char *message);
|
||||
};
|
||||
|
||||
} // namespace esphome::esp32_ble_client
|
||||
|
@@ -58,10 +58,10 @@ void GroveGasMultichannelV2Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_)
|
||||
LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_)
|
||||
LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_)
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_)
|
||||
LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_);
|
||||
LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_);
|
||||
LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_);
|
||||
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_);
|
||||
|
||||
if (this->is_failed()) {
|
||||
switch (this->error_code_) {
|
||||
|
@@ -43,10 +43,10 @@ void HLW8012Component::dump_config() {
|
||||
" Voltage Divider: %.1f",
|
||||
this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_);
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_)
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_)
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_)
|
||||
LOG_SENSOR(" ", "Energy", this->energy_sensor_)
|
||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
LOG_SENSOR(" ", "Energy", this->energy_sensor_);
|
||||
}
|
||||
float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void HLW8012Component::update() {
|
||||
|
@@ -12,7 +12,7 @@ void HTE501Component::setup() {
|
||||
this->write(address, 2, false);
|
||||
uint8_t identification[9];
|
||||
this->read(identification, 9);
|
||||
if (identification[8] != calc_crc8_(identification, 0, 7)) {
|
||||
if (identification[8] != crc8(identification, 8, 0xFF, 0x31, true)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -46,7 +46,8 @@ void HTE501Component::update() {
|
||||
this->set_timeout(50, [this]() {
|
||||
uint8_t i2c_response[6];
|
||||
this->read(i2c_response, 6);
|
||||
if (i2c_response[2] != calc_crc8_(i2c_response, 0, 1) && i2c_response[5] != calc_crc8_(i2c_response, 3, 4)) {
|
||||
if (i2c_response[2] != crc8(i2c_response, 2, 0xFF, 0x31, true) &&
|
||||
i2c_response[5] != crc8(i2c_response + 3, 2, 0xFF, 0x31, true)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -67,24 +68,5 @@ void HTE501Component::update() {
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
unsigned char HTE501Component::calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to) {
|
||||
unsigned char crc_val = 0xFF;
|
||||
unsigned char i = 0;
|
||||
unsigned char j = 0;
|
||||
for (i = from; i <= to; i++) {
|
||||
int cur_val = buf[i];
|
||||
for (j = 0; j < 8; j++) {
|
||||
if (((crc_val ^ cur_val) & 0x80) != 0) // If MSBs are not equal
|
||||
{
|
||||
crc_val = ((crc_val << 1) ^ 0x31);
|
||||
} else {
|
||||
crc_val = (crc_val << 1);
|
||||
}
|
||||
cur_val = cur_val << 1;
|
||||
}
|
||||
}
|
||||
return crc_val;
|
||||
}
|
||||
} // namespace hte501
|
||||
} // namespace esphome
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hte501 {
|
||||
@@ -19,7 +19,6 @@ class HTE501Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
unsigned char calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to);
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "lc709203f.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lc709203f {
|
||||
@@ -189,7 +190,7 @@ uint8_t Lc709203f::get_register_(uint8_t register_to_read, uint16_t *register_va
|
||||
// Error on the i2c bus
|
||||
this->status_set_warning(
|
||||
str_sprintf("Error code %d when reading from register 0x%02X", return_code, register_to_read).c_str());
|
||||
} else if (this->crc8_(read_buffer, 5) != read_buffer[5]) {
|
||||
} else if (crc8(read_buffer, 5, 0x00, 0x07, true) != read_buffer[5]) {
|
||||
// I2C indicated OK, but the CRC of the data does not matcth.
|
||||
this->status_set_warning(str_sprintf("CRC error reading from register 0x%02X", register_to_read).c_str());
|
||||
} else {
|
||||
@@ -220,7 +221,7 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set)
|
||||
write_buffer[1] = register_to_set;
|
||||
write_buffer[2] = value_to_set & 0xFF; // Low byte
|
||||
write_buffer[3] = (value_to_set >> 8) & 0xFF; // High byte
|
||||
write_buffer[4] = this->crc8_(write_buffer, 4);
|
||||
write_buffer[4] = crc8(write_buffer, 4, 0x00, 0x07, true);
|
||||
|
||||
for (uint8_t i = 0; i <= LC709203F_I2C_RETRY_COUNT; i++) {
|
||||
// Note: we don't write the first byte of the write buffer to the device.
|
||||
@@ -239,20 +240,6 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set)
|
||||
return return_code;
|
||||
}
|
||||
|
||||
uint8_t Lc709203f::crc8_(uint8_t *byte_buffer, uint8_t length_of_crc) {
|
||||
uint8_t crc = 0x00;
|
||||
const uint8_t polynomial(0x07);
|
||||
|
||||
for (uint8_t j = length_of_crc; j; --j) {
|
||||
crc ^= *byte_buffer++;
|
||||
|
||||
for (uint8_t i = 8; i; --i) {
|
||||
crc = (crc & 0x80) ? (crc << 1) ^ polynomial : (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void Lc709203f::set_pack_size(uint16_t pack_size) {
|
||||
static const uint16_t PACK_SIZE_ARRAY[6] = {100, 200, 500, 1000, 2000, 3000};
|
||||
static const uint16_t APA_ARRAY[6] = {0x08, 0x0B, 0x10, 0x19, 0x2D, 0x36};
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lc709203f {
|
||||
@@ -38,7 +38,6 @@ class Lc709203f : public sensor::Sensor, public PollingComponent, public i2c::I2
|
||||
private:
|
||||
uint8_t get_register_(uint8_t register_to_read, uint16_t *register_value);
|
||||
uint8_t set_register_(uint8_t register_to_set, uint16_t value_to_set);
|
||||
uint8_t crc8_(uint8_t *byte_buffer, uint8_t length_of_crc);
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include "text_sensor.h"
|
||||
#include "ld2420_text_sensor.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
@@ -5,7 +5,7 @@
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <flashdb.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
@@ -139,21 +139,29 @@ class LibreTinyPreferences : public ESPPreferences {
|
||||
}
|
||||
|
||||
bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) {
|
||||
NVSData stored_data{};
|
||||
struct fdb_kv kv;
|
||||
fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv);
|
||||
if (kvp == nullptr) {
|
||||
ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str());
|
||||
return true;
|
||||
}
|
||||
stored_data.data.resize(kv.value_len);
|
||||
fdb_blob_make(&blob, stored_data.data.data(), kv.value_len);
|
||||
|
||||
// Check size first - if different, data has changed
|
||||
if (kv.value_len != to_save.data.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allocate buffer on heap to avoid stack allocation for large data
|
||||
auto stored_data = std::make_unique<uint8_t[]>(kv.value_len);
|
||||
fdb_blob_make(&blob, stored_data.get(), kv.value_len);
|
||||
size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob);
|
||||
if (actual_len != kv.value_len) {
|
||||
ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len);
|
||||
return true;
|
||||
}
|
||||
return to_save.data != stored_data.data;
|
||||
|
||||
// Compare the actual data
|
||||
return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0;
|
||||
}
|
||||
|
||||
bool reset() override {
|
||||
|
@@ -44,6 +44,13 @@ class AddressableLightEffect : public LightEffect {
|
||||
this->apply(*this->get_addressable_(), current_color);
|
||||
}
|
||||
|
||||
/// Get effect index specifically for addressable effects.
|
||||
/// Can be used by effects to modify behavior based on their position in the list.
|
||||
uint32_t get_effect_index() const { return this->get_index(); }
|
||||
|
||||
/// Check if this is the currently running addressable effect.
|
||||
bool is_current_effect() const { return this->is_active() && this->get_addressable_()->is_effect_active(); }
|
||||
|
||||
protected:
|
||||
AddressableLight *get_addressable_() const { return (AddressableLight *) this->state_->get_output(); }
|
||||
};
|
||||
|
@@ -125,6 +125,10 @@ class LambdaLightEffect : public LightEffect {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current effect index for use in lambda functions.
|
||||
/// This can be useful for lambda effects that need to know their own index.
|
||||
uint32_t get_current_index() const { return this->get_index(); }
|
||||
|
||||
protected:
|
||||
std::function<void(bool initial_run)> f_;
|
||||
uint32_t update_interval_;
|
||||
@@ -143,6 +147,10 @@ class AutomationLightEffect : public LightEffect {
|
||||
}
|
||||
Trigger<> *get_trig() const { return trig_; }
|
||||
|
||||
/// Get the current effect index for use in automations.
|
||||
/// Useful for automations that need to know which effect is running.
|
||||
uint32_t get_current_index() const { return this->get_index(); }
|
||||
|
||||
protected:
|
||||
Trigger<> *trig_{new Trigger<>};
|
||||
};
|
||||
|
36
esphome/components/light/light_effect.cpp
Normal file
36
esphome/components/light/light_effect.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "light_effect.h"
|
||||
#include "light_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
uint32_t LightEffect::get_index() const {
|
||||
if (this->state_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return this->get_index_in_parent_();
|
||||
}
|
||||
|
||||
bool LightEffect::is_active() const {
|
||||
if (this->state_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return this->get_index() != 0 && this->state_->get_current_effect_index() == this->get_index();
|
||||
}
|
||||
|
||||
uint32_t LightEffect::get_index_in_parent_() const {
|
||||
if (this->state_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto &effects = this->state_->get_effects();
|
||||
for (size_t i = 0; i < effects.size(); i++) {
|
||||
if (effects[i] == this) {
|
||||
return i + 1; // Effects are 1-indexed in the API
|
||||
}
|
||||
}
|
||||
return 0; // Not found
|
||||
}
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
@@ -34,9 +34,23 @@ class LightEffect {
|
||||
this->init();
|
||||
}
|
||||
|
||||
/// Get the index of this effect in the parent light's effect list.
|
||||
/// Returns 0 if not found or not initialized.
|
||||
uint32_t get_index() const;
|
||||
|
||||
/// Check if this effect is currently active.
|
||||
bool is_active() const;
|
||||
|
||||
/// Get a reference to the parent light state.
|
||||
/// Returns nullptr if not initialized.
|
||||
LightState *get_light_state() const { return this->state_; }
|
||||
|
||||
protected:
|
||||
LightState *state_{nullptr};
|
||||
std::string name_;
|
||||
|
||||
/// Internal method to find this effect's index in the parent light's effect list.
|
||||
uint32_t get_index_in_parent_() const;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
|
@@ -36,8 +36,11 @@ static constexpr const char *get_color_mode_json_str(ColorMode mode) {
|
||||
|
||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
if (state.supports_effects())
|
||||
if (state.supports_effects()) {
|
||||
root["effect"] = state.get_effect_name();
|
||||
root["effect_index"] = state.get_current_effect_index();
|
||||
root["effect_count"] = state.get_effect_count();
|
||||
}
|
||||
|
||||
auto values = state.remote_values;
|
||||
auto traits = state.get_output()->get_traits();
|
||||
@@ -160,6 +163,11 @@ void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject
|
||||
const char *effect = root["effect"];
|
||||
call.set_effect(effect);
|
||||
}
|
||||
|
||||
if (root["effect_index"].is<uint32_t>()) {
|
||||
uint32_t effect_index = root["effect_index"];
|
||||
call.set_effect(effect_index);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace light
|
||||
|
@@ -140,12 +140,22 @@ float LightState::get_setup_priority() const { return setup_priority::HARDWARE -
|
||||
void LightState::publish_state() { this->remote_values_callback_.call(); }
|
||||
|
||||
LightOutput *LightState::get_output() const { return this->output_; }
|
||||
|
||||
static constexpr const char *EFFECT_NONE = "None";
|
||||
static constexpr auto EFFECT_NONE_REF = StringRef::from_lit("None");
|
||||
|
||||
std::string LightState::get_effect_name() {
|
||||
if (this->active_effect_index_ > 0) {
|
||||
return this->effects_[this->active_effect_index_ - 1]->get_name();
|
||||
} else {
|
||||
return "None";
|
||||
}
|
||||
return EFFECT_NONE;
|
||||
}
|
||||
|
||||
StringRef LightState::get_effect_name_ref() {
|
||||
if (this->active_effect_index_ > 0) {
|
||||
return StringRef(this->effects_[this->active_effect_index_ - 1]->get_name());
|
||||
}
|
||||
return EFFECT_NONE_REF;
|
||||
}
|
||||
|
||||
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "light_call.h"
|
||||
#include "light_color_values.h"
|
||||
#include "light_effect.h"
|
||||
@@ -116,6 +117,8 @@ class LightState : public EntityBase, public Component {
|
||||
|
||||
/// Return the name of the current effect, or if no effect is active "None".
|
||||
std::string get_effect_name();
|
||||
/// Return the name of the current effect as StringRef (for API usage)
|
||||
StringRef get_effect_name_ref();
|
||||
|
||||
/**
|
||||
* This lets front-end components subscribe to light change events. This callback is called once
|
||||
@@ -160,6 +163,44 @@ class LightState : public EntityBase, public Component {
|
||||
/// Add effects for this light state.
|
||||
void add_effects(const std::vector<LightEffect *> &effects);
|
||||
|
||||
/// Get the total number of effects available for this light.
|
||||
size_t get_effect_count() const { return this->effects_.size(); }
|
||||
|
||||
/// Get the currently active effect index (0 = no effect, 1+ = effect index).
|
||||
uint32_t get_current_effect_index() const { return this->active_effect_index_; }
|
||||
|
||||
/// Get effect index by name. Returns 0 if effect not found.
|
||||
uint32_t get_effect_index(const std::string &effect_name) const {
|
||||
if (strcasecmp(effect_name.c_str(), "none") == 0) {
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = 0; i < this->effects_.size(); i++) {
|
||||
if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name().c_str()) == 0) {
|
||||
return i + 1; // Effects are 1-indexed in active_effect_index_
|
||||
}
|
||||
}
|
||||
return 0; // Effect not found
|
||||
}
|
||||
|
||||
/// Get effect by index. Returns nullptr if index is invalid.
|
||||
LightEffect *get_effect_by_index(uint32_t index) const {
|
||||
if (index == 0 || index > this->effects_.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return this->effects_[index - 1]; // Effects are 1-indexed in active_effect_index_
|
||||
}
|
||||
|
||||
/// Get effect name by index. Returns "None" for index 0, empty string for invalid index.
|
||||
std::string get_effect_name_by_index(uint32_t index) const {
|
||||
if (index == 0) {
|
||||
return "None";
|
||||
}
|
||||
if (index > this->effects_.size()) {
|
||||
return ""; // Invalid index
|
||||
}
|
||||
return this->effects_[index - 1]->get_name();
|
||||
}
|
||||
|
||||
/// The result of all the current_values_as_* methods have gamma correction applied.
|
||||
void current_values_as_binary(bool *binary);
|
||||
|
||||
|
@@ -24,100 +24,139 @@ static const char *const TAG = "mdns";
|
||||
void MDNSComponent::compile_records_() {
|
||||
this->hostname_ = App.get_name();
|
||||
|
||||
this->services_.clear();
|
||||
// Calculate exact capacity needed for services vector
|
||||
size_t services_count = 0;
|
||||
#ifdef USE_API
|
||||
if (api::global_api_server != nullptr) {
|
||||
MDNSService service{};
|
||||
services_count++;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_PROMETHEUS
|
||||
services_count++;
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER
|
||||
services_count++;
|
||||
#endif
|
||||
#ifdef USE_MDNS_EXTRA_SERVICES
|
||||
services_count += this->services_extra_.size();
|
||||
#endif
|
||||
// Reserve for fallback service if needed
|
||||
if (services_count == 0) {
|
||||
services_count = 1;
|
||||
}
|
||||
this->services_.reserve(services_count);
|
||||
|
||||
#ifdef USE_API
|
||||
if (api::global_api_server != nullptr) {
|
||||
this->services_.emplace_back();
|
||||
auto &service = this->services_.back();
|
||||
service.service_type = "_esphomelib";
|
||||
service.proto = "_tcp";
|
||||
service.port = api::global_api_server->get_port();
|
||||
if (!App.get_friendly_name().empty()) {
|
||||
service.txt_records.push_back({"friendly_name", App.get_friendly_name()});
|
||||
}
|
||||
service.txt_records.push_back({"version", ESPHOME_VERSION});
|
||||
service.txt_records.push_back({"mac", get_mac_address()});
|
||||
const char *platform = nullptr;
|
||||
#ifdef USE_ESP8266
|
||||
platform = "ESP8266";
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
platform = "ESP32";
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
platform = "RP2040";
|
||||
#endif
|
||||
#ifdef USE_LIBRETINY
|
||||
platform = lt_cpu_get_model_name();
|
||||
#endif
|
||||
if (platform != nullptr) {
|
||||
service.txt_records.push_back({"platform", platform});
|
||||
}
|
||||
|
||||
service.txt_records.push_back({"board", ESPHOME_BOARD});
|
||||
const std::string &friendly_name = App.get_friendly_name();
|
||||
bool friendly_name_empty = friendly_name.empty();
|
||||
|
||||
// Calculate exact capacity for txt_records
|
||||
size_t txt_count = 3; // version, mac, board (always present)
|
||||
if (!friendly_name_empty) {
|
||||
txt_count++; // friendly_name
|
||||
}
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
txt_count++; // platform
|
||||
#endif
|
||||
#if defined(USE_WIFI) || defined(USE_ETHERNET) || defined(USE_OPENTHREAD)
|
||||
txt_count++; // network
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
txt_count++; // api_encryption or api_encryption_supported
|
||||
#endif
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
txt_count += 2; // project_name and project_version
|
||||
#endif
|
||||
#ifdef USE_DASHBOARD_IMPORT
|
||||
txt_count++; // package_import_url
|
||||
#endif
|
||||
|
||||
auto &txt_records = service.txt_records;
|
||||
txt_records.reserve(txt_count);
|
||||
|
||||
if (!friendly_name_empty) {
|
||||
txt_records.emplace_back(MDNSTXTRecord{"friendly_name", friendly_name});
|
||||
}
|
||||
txt_records.emplace_back(MDNSTXTRecord{"version", ESPHOME_VERSION});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"mac", get_mac_address()});
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP8266"});
|
||||
#elif defined(USE_ESP32)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP32"});
|
||||
#elif defined(USE_RP2040)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", "RP2040"});
|
||||
#elif defined(USE_LIBRETINY)
|
||||
txt_records.emplace_back(MDNSTXTRecord{"platform", lt_cpu_get_model_name()});
|
||||
#endif
|
||||
|
||||
txt_records.emplace_back(MDNSTXTRecord{"board", ESPHOME_BOARD});
|
||||
|
||||
#if defined(USE_WIFI)
|
||||
service.txt_records.push_back({"network", "wifi"});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"network", "wifi"});
|
||||
#elif defined(USE_ETHERNET)
|
||||
service.txt_records.push_back({"network", "ethernet"});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"network", "ethernet"});
|
||||
#elif defined(USE_OPENTHREAD)
|
||||
service.txt_records.push_back({"network", "thread"});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"network", "thread"});
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
static constexpr const char *NOISE_ENCRYPTION = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
|
||||
if (api::global_api_server->get_noise_ctx()->has_psk()) {
|
||||
service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"api_encryption", NOISE_ENCRYPTION});
|
||||
} else {
|
||||
service.txt_records.push_back({"api_encryption_supported", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"api_encryption_supported", NOISE_ENCRYPTION});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME});
|
||||
service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"project_name", ESPHOME_PROJECT_NAME});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"project_version", ESPHOME_PROJECT_VERSION});
|
||||
#endif // ESPHOME_PROJECT_NAME
|
||||
|
||||
#ifdef USE_DASHBOARD_IMPORT
|
||||
service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()});
|
||||
txt_records.emplace_back(MDNSTXTRecord{"package_import_url", dashboard_import::get_package_import_url()});
|
||||
#endif
|
||||
|
||||
this->services_.push_back(service);
|
||||
}
|
||||
#endif // USE_API
|
||||
|
||||
#ifdef USE_PROMETHEUS
|
||||
{
|
||||
MDNSService service{};
|
||||
service.service_type = "_prometheus-http";
|
||||
service.proto = "_tcp";
|
||||
service.port = USE_WEBSERVER_PORT;
|
||||
this->services_.push_back(service);
|
||||
}
|
||||
this->services_.emplace_back();
|
||||
auto &prom_service = this->services_.back();
|
||||
prom_service.service_type = "_prometheus-http";
|
||||
prom_service.proto = "_tcp";
|
||||
prom_service.port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
{
|
||||
MDNSService service{};
|
||||
service.service_type = "_http";
|
||||
service.proto = "_tcp";
|
||||
service.port = USE_WEBSERVER_PORT;
|
||||
this->services_.push_back(service);
|
||||
}
|
||||
this->services_.emplace_back();
|
||||
auto &web_service = this->services_.back();
|
||||
web_service.service_type = "_http";
|
||||
web_service.proto = "_tcp";
|
||||
web_service.port = USE_WEBSERVER_PORT;
|
||||
#endif
|
||||
|
||||
#ifdef USE_MDNS_EXTRA_SERVICES
|
||||
this->services_.insert(this->services_.end(), this->services_extra_.begin(), this->services_extra_.end());
|
||||
#endif
|
||||
|
||||
if (this->services_.empty()) {
|
||||
// Publish "http" service if not using native API
|
||||
#if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES)
|
||||
// Publish "http" service if not using native API or any other services
|
||||
// This is just to have *some* mDNS service so that .local resolution works
|
||||
MDNSService service{};
|
||||
service.service_type = "_http";
|
||||
service.proto = "_tcp";
|
||||
service.port = USE_WEBSERVER_PORT;
|
||||
service.txt_records.push_back({"version", ESPHOME_VERSION});
|
||||
this->services_.push_back(service);
|
||||
}
|
||||
this->services_.emplace_back();
|
||||
auto &fallback_service = this->services_.back();
|
||||
fallback_service.service_type = "_http";
|
||||
fallback_service.proto = "_tcp";
|
||||
fallback_service.port = USE_WEBSERVER_PORT;
|
||||
fallback_service.txt_records.emplace_back(MDNSTXTRecord{"version", ESPHOME_VERSION});
|
||||
#endif
|
||||
}
|
||||
|
||||
void MDNSComponent::dump_config() {
|
||||
@@ -125,6 +164,7 @@ void MDNSComponent::dump_config() {
|
||||
"mDNS:\n"
|
||||
" Hostname: %s",
|
||||
this->hostname_.c_str());
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
ESP_LOGV(TAG, " Services:");
|
||||
for (const auto &service : this->services_) {
|
||||
ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(),
|
||||
@@ -134,6 +174,7 @@ void MDNSComponent::dump_config() {
|
||||
const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<MDNSService> MDNSComponent::get_services() { return this->services_; }
|
||||
|
@@ -50,28 +50,13 @@ bool MLX90614Component::write_emissivity_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) {
|
||||
uint8_t crc = 0;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
uint8_t in = data[i];
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
bool carry = (crc ^ in) & 0x80;
|
||||
crc <<= 1;
|
||||
if (carry)
|
||||
crc ^= 0x07;
|
||||
in <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) {
|
||||
uint8_t buf[5];
|
||||
buf[0] = this->address_ << 1;
|
||||
buf[1] = reg;
|
||||
buf[2] = data & 0xFF;
|
||||
buf[3] = data >> 8;
|
||||
buf[4] = this->crc8_pec_(buf, 4);
|
||||
buf[4] = crc8(buf, 4, 0x00, 0x07, true);
|
||||
return this->write_bytes(reg, buf + 2, 3);
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,6 @@ class MLX90614Component : public PollingComponent, public i2c::I2CDevice {
|
||||
protected:
|
||||
bool write_emissivity_();
|
||||
|
||||
uint8_t crc8_pec_(const uint8_t *data, uint8_t len);
|
||||
bool write_bytes_(uint8_t reg, uint16_t data);
|
||||
|
||||
sensor::Sensor *ambient_sensor_{nullptr};
|
||||
|
@@ -11,7 +11,7 @@ void NTC::setup() {
|
||||
if (this->sensor_->has_state())
|
||||
this->process_(this->sensor_->state);
|
||||
}
|
||||
void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this) }
|
||||
void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this); }
|
||||
float NTC::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void NTC::process_(float value) {
|
||||
if (std::isnan(value)) {
|
||||
|
@@ -6,6 +6,27 @@ namespace number {
|
||||
|
||||
static const char *const TAG = "number";
|
||||
|
||||
// Function implementation of LOG_NUMBER macro to reduce code size
|
||||
void log_number(const char *tag, const char *prefix, const char *type, Number *obj) {
|
||||
if (obj == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||
|
||||
if (!obj->get_icon().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str());
|
||||
}
|
||||
|
||||
if (!obj->traits.get_unit_of_measurement().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Unit of Measurement: '%s'", prefix, obj->traits.get_unit_of_measurement().c_str());
|
||||
}
|
||||
|
||||
if (!obj->traits.get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->traits.get_device_class().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Number::publish_state(float state) {
|
||||
this->set_has_state(true);
|
||||
this->state = state;
|
||||
|
@@ -9,19 +9,10 @@
|
||||
namespace esphome {
|
||||
namespace number {
|
||||
|
||||
#define LOG_NUMBER(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
if (!(obj)->traits.get_unit_of_measurement().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->traits.get_unit_of_measurement().c_str()); \
|
||||
} \
|
||||
if (!(obj)->traits.get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->traits.get_device_class().c_str()); \
|
||||
} \
|
||||
}
|
||||
class Number;
|
||||
void log_number(const char *tag, const char *prefix, const char *type, Number *obj);
|
||||
|
||||
#define LOG_NUMBER(prefix, type, obj) log_number(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||
|
||||
#define SUB_NUMBER(name) \
|
||||
protected: \
|
||||
|
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <vector>
|
||||
|
||||
#include "opentherm.h"
|
||||
|
||||
@@ -17,21 +17,21 @@
|
||||
#endif
|
||||
|
||||
#ifdef OPENTHERM_USE_SWITCH
|
||||
#include "esphome/components/opentherm/switch/switch.h"
|
||||
#include "esphome/components/opentherm/switch/opentherm_switch.h"
|
||||
#endif
|
||||
|
||||
#ifdef OPENTHERM_USE_OUTPUT
|
||||
#include "esphome/components/opentherm/output/output.h"
|
||||
#include "esphome/components/opentherm/output/opentherm_output.h"
|
||||
#endif
|
||||
|
||||
#ifdef OPENTHERM_USE_NUMBER
|
||||
#include "esphome/components/opentherm/number/number.h"
|
||||
#include "esphome/components/opentherm/number/opentherm_number.h"
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
|
||||
#include "opentherm_macros.h"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#include "number.h"
|
||||
#include "opentherm_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace opentherm {
|
@@ -1,5 +1,5 @@
|
||||
#include "esphome/core/helpers.h" // for clamp() and lerp()
|
||||
#include "output.h"
|
||||
#include "opentherm_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace opentherm {
|
@@ -1,4 +1,4 @@
|
||||
#include "switch.h"
|
||||
#include "opentherm_switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace opentherm {
|
@@ -26,7 +26,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield uart.register_uart_device(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
@@ -99,9 +99,9 @@ async def to_code(config):
|
||||
}
|
||||
),
|
||||
)
|
||||
def output_pipsolar_set_level_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
async def output_pipsolar_set_level_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = yield cg.templatable(config[CONF_VALUE], args, float)
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, float)
|
||||
cg.add(var.set_level(template_))
|
||||
yield var
|
||||
return var
|
||||
|
@@ -17,7 +17,7 @@ void IRAM_ATTR PulseWidthSensorStore::gpio_intr(PulseWidthSensorStore *arg) {
|
||||
}
|
||||
|
||||
void PulseWidthSensor::dump_config() {
|
||||
LOG_SENSOR("", "Pulse Width", this)
|
||||
LOG_SENSOR("", "Pulse Width", this);
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
}
|
||||
|
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
@@ -1782,14 +1782,12 @@ def nexa_dumper(var, config):
|
||||
|
||||
|
||||
@register_action("nexa", NexaAction, NEXA_SCHEMA)
|
||||
def nexa_action(var, config, args):
|
||||
cg.add(var.set_device((yield cg.templatable(config[CONF_DEVICE], args, cg.uint32))))
|
||||
cg.add(var.set_group((yield cg.templatable(config[CONF_GROUP], args, cg.uint8))))
|
||||
cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, cg.uint8))))
|
||||
cg.add(
|
||||
var.set_channel((yield cg.templatable(config[CONF_CHANNEL], args, cg.uint8)))
|
||||
)
|
||||
cg.add(var.set_level((yield cg.templatable(config[CONF_LEVEL], args, cg.uint8))))
|
||||
async def nexa_action(var, config, args):
|
||||
cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint32)))
|
||||
cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.uint8)))
|
||||
cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, cg.uint8)))
|
||||
cg.add(var.set_channel(await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)))
|
||||
cg.add(var.set_level(await cg.templatable(config[CONF_LEVEL], args, cg.uint8)))
|
||||
|
||||
|
||||
# Midea
|
||||
|
@@ -6,6 +6,33 @@ namespace sensor {
|
||||
|
||||
static const char *const TAG = "sensor";
|
||||
|
||||
// Function implementation of LOG_SENSOR macro to reduce code size
|
||||
void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *obj) {
|
||||
if (obj == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(tag,
|
||||
"%s%s '%s'\n"
|
||||
"%s State Class: '%s'\n"
|
||||
"%s Unit of Measurement: '%s'\n"
|
||||
"%s Accuracy Decimals: %d",
|
||||
prefix, type, obj->get_name().c_str(), prefix, state_class_to_string(obj->get_state_class()).c_str(),
|
||||
prefix, obj->get_unit_of_measurement().c_str(), prefix, obj->get_accuracy_decimals());
|
||||
|
||||
if (!obj->get_device_class().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
|
||||
}
|
||||
|
||||
if (!obj->get_icon().empty()) {
|
||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str());
|
||||
}
|
||||
|
||||
if (obj->get_force_update()) {
|
||||
ESP_LOGV(tag, "%s Force Update: YES", prefix);
|
||||
}
|
||||
}
|
||||
|
||||
std::string state_class_to_string(StateClass state_class) {
|
||||
switch (state_class) {
|
||||
case STATE_CLASS_MEASUREMENT:
|
||||
|
@@ -12,26 +12,9 @@
|
||||
namespace esphome {
|
||||
namespace sensor {
|
||||
|
||||
#define LOG_SENSOR(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, \
|
||||
"%s%s '%s'\n" \
|
||||
"%s State Class: '%s'\n" \
|
||||
"%s Unit of Measurement: '%s'\n" \
|
||||
"%s Accuracy Decimals: %d", \
|
||||
prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str(), prefix, \
|
||||
state_class_to_string((obj)->get_state_class()).c_str(), prefix, \
|
||||
(obj)->get_unit_of_measurement().c_str(), prefix, (obj)->get_accuracy_decimals()); \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
if ((obj)->get_force_update()) { \
|
||||
ESP_LOGV(TAG, "%s Force Update: YES", prefix); \
|
||||
} \
|
||||
}
|
||||
void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *obj);
|
||||
|
||||
#define LOG_SENSOR(prefix, type, obj) log_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
|
||||
|
||||
#define SUB_SENSOR(name) \
|
||||
protected: \
|
||||
|
@@ -183,7 +183,7 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
fw_hex = get_firmware(config[CONF_FIRMWARE])
|
||||
fw_major, fw_minor = parse_firmware_version(config[CONF_FIRMWARE][CONF_VERSION])
|
||||
|
||||
@@ -193,17 +193,17 @@ def to_code(config):
|
||||
cg.add_define("USE_SHD_FIRMWARE_MINOR_VERSION", fw_minor)
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
yield cg.register_component(var, config)
|
||||
await cg.register_component(var, config)
|
||||
config.pop(
|
||||
CONF_UPDATE_INTERVAL
|
||||
) # drop UPDATE_INTERVAL as it does not apply to the light component
|
||||
|
||||
yield light.register_light(var, config)
|
||||
yield uart.register_uart_device(var, config)
|
||||
await light.register_light(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
nrst_pin = yield cg.gpio_pin_expression(config[CONF_NRST_PIN])
|
||||
nrst_pin = await cg.gpio_pin_expression(config[CONF_NRST_PIN])
|
||||
cg.add(var.set_nrst_pin(nrst_pin))
|
||||
boot0_pin = yield cg.gpio_pin_expression(config[CONF_BOOT0_PIN])
|
||||
boot0_pin = await cg.gpio_pin_expression(config[CONF_BOOT0_PIN])
|
||||
cg.add(var.set_boot0_pin(boot0_pin))
|
||||
|
||||
cg.add(var.set_leading_edge(config[CONF_LEADING_EDGE]))
|
||||
@@ -217,5 +217,5 @@ def to_code(config):
|
||||
continue
|
||||
|
||||
conf = config[key]
|
||||
sens = yield sensor.new_sensor(conf)
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
|
@@ -12,7 +12,7 @@ void TEE501Component::setup() {
|
||||
this->write(address, 2, false);
|
||||
uint8_t identification[9];
|
||||
this->read(identification, 9);
|
||||
if (identification[8] != calc_crc8_(identification, 0, 7)) {
|
||||
if (identification[8] != crc8(identification, 8, 0xFF, 0x31, true)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -45,7 +45,7 @@ void TEE501Component::update() {
|
||||
this->set_timeout(50, [this]() {
|
||||
uint8_t i2c_response[3];
|
||||
this->read(i2c_response, 3);
|
||||
if (i2c_response[2] != calc_crc8_(i2c_response, 0, 1)) {
|
||||
if (i2c_response[2] != crc8(i2c_response, 2, 0xFF, 0x31, true)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -62,24 +62,5 @@ void TEE501Component::update() {
|
||||
});
|
||||
}
|
||||
|
||||
unsigned char TEE501Component::calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to) {
|
||||
unsigned char crc_val = 0xFF;
|
||||
unsigned char i = 0;
|
||||
unsigned char j = 0;
|
||||
for (i = from; i <= to; i++) {
|
||||
int cur_val = buf[i];
|
||||
for (j = 0; j < 8; j++) {
|
||||
if (((crc_val ^ cur_val) & 0x80) != 0) // If MSBs are not equal
|
||||
{
|
||||
crc_val = ((crc_val << 1) ^ 0x31);
|
||||
} else {
|
||||
crc_val = (crc_val << 1);
|
||||
}
|
||||
cur_val = cur_val << 1;
|
||||
}
|
||||
}
|
||||
return crc_val;
|
||||
}
|
||||
|
||||
} // namespace tee501
|
||||
} // namespace esphome
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tee501 {
|
||||
@@ -16,8 +16,6 @@ class TEE501Component : public sensor::Sensor, public PollingComponent, public i
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
unsigned char calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to);
|
||||
|
||||
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, CRC_CHECK_FAILED } error_code_{NONE};
|
||||
};
|
||||
|
||||
|
@@ -105,9 +105,9 @@ void UFireECComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "uFire-EC");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_)
|
||||
LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_)
|
||||
LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_)
|
||||
LOG_SENSOR(" ", "EC Sensor", this->ec_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Temperature Compensation: %f\n"
|
||||
" Temperature Coefficient: %f",
|
||||
|
@@ -142,9 +142,9 @@ void UFireISEComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "uFire-ISE");
|
||||
LOG_I2C_DEVICE(this)
|
||||
LOG_UPDATE_INTERVAL(this)
|
||||
LOG_SENSOR(" ", "PH Sensor", this->ph_sensor_)
|
||||
LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_)
|
||||
LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_)
|
||||
LOG_SENSOR(" ", "PH Sensor", this->ph_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature Sensor", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature Sensor external", this->temperature_sensor_external_);
|
||||
}
|
||||
|
||||
} // namespace ufire_ise
|
||||
|
@@ -202,9 +202,9 @@ async def valve_stop_to_code(config, action_id, template_arg, args):
|
||||
|
||||
|
||||
@automation.register_action("valve.toggle", ToggleAction, VALVE_ACTION_SCHEMA)
|
||||
def valve_toggle_to_code(config, action_id, template_arg, args):
|
||||
paren = yield cg.get_variable(config[CONF_ID])
|
||||
yield cg.new_Pvariable(action_id, template_arg, paren)
|
||||
async def valve_toggle_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
VALVE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
|
@@ -507,14 +507,37 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
auto detail = get_request_detail(request);
|
||||
std::string data = this->switch_json(obj, obj->state, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals("toggle")) {
|
||||
this->defer([obj]() { obj->toggle(); });
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle action methods with single defer and response
|
||||
enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF };
|
||||
SwitchAction action = NONE;
|
||||
|
||||
if (match.method_equals("toggle")) {
|
||||
action = TOGGLE;
|
||||
} else if (match.method_equals("turn_on")) {
|
||||
this->defer([obj]() { obj->turn_on(); });
|
||||
request->send(200);
|
||||
action = TURN_ON;
|
||||
} else if (match.method_equals("turn_off")) {
|
||||
this->defer([obj]() { obj->turn_off(); });
|
||||
action = TURN_OFF;
|
||||
}
|
||||
|
||||
if (action != NONE) {
|
||||
this->defer([obj, action]() {
|
||||
switch (action) {
|
||||
case TOGGLE:
|
||||
obj->toggle();
|
||||
break;
|
||||
case TURN_ON:
|
||||
obj->turn_on();
|
||||
break;
|
||||
case TURN_OFF:
|
||||
obj->turn_off();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
@@ -1332,14 +1355,37 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
|
||||
auto detail = get_request_detail(request);
|
||||
std::string data = this->lock_json(obj, obj->state, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals("lock")) {
|
||||
this->defer([obj]() { obj->lock(); });
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle action methods with single defer and response
|
||||
enum LockAction { NONE, LOCK, UNLOCK, OPEN };
|
||||
LockAction action = NONE;
|
||||
|
||||
if (match.method_equals("lock")) {
|
||||
action = LOCK;
|
||||
} else if (match.method_equals("unlock")) {
|
||||
this->defer([obj]() { obj->unlock(); });
|
||||
request->send(200);
|
||||
action = UNLOCK;
|
||||
} else if (match.method_equals("open")) {
|
||||
this->defer([obj]() { obj->open(); });
|
||||
action = OPEN;
|
||||
}
|
||||
|
||||
if (action != NONE) {
|
||||
this->defer([obj, action]() {
|
||||
switch (action) {
|
||||
case LOCK:
|
||||
obj->lock();
|
||||
break;
|
||||
case UNLOCK:
|
||||
obj->unlock();
|
||||
break;
|
||||
case OPEN:
|
||||
obj->open();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
|
@@ -256,30 +256,79 @@ void Application::run_powerdown_hooks() {
|
||||
void Application::teardown_components(uint32_t timeout_ms) {
|
||||
uint32_t start_time = millis();
|
||||
|
||||
// Copy all components in reverse order using reverse iterators
|
||||
// Use a StaticVector instead of std::vector to avoid heap allocation
|
||||
// since we know the actual size at compile time
|
||||
StaticVector<Component *, ESPHOME_COMPONENT_COUNT> pending_components;
|
||||
|
||||
// Copy all components in reverse order
|
||||
// Reverse order matches the behavior of run_safe_shutdown_hooks() above and ensures
|
||||
// components are torn down in the opposite order of their setup_priority (which is
|
||||
// used to sort components during Application::setup())
|
||||
std::vector<Component *> pending_components(this->components_.rbegin(), this->components_.rend());
|
||||
size_t num_components = this->components_.size();
|
||||
for (size_t i = 0; i < num_components; ++i) {
|
||||
pending_components[i] = this->components_[num_components - 1 - i];
|
||||
}
|
||||
|
||||
uint32_t now = start_time;
|
||||
while (!pending_components.empty() && (now - start_time) < timeout_ms) {
|
||||
size_t pending_count = num_components;
|
||||
|
||||
// Teardown Algorithm
|
||||
// ==================
|
||||
// We iterate through pending components, calling teardown() on each.
|
||||
// Components that return false (need more time) are copied forward
|
||||
// in the array. Components that return true (finished) are skipped.
|
||||
//
|
||||
// The compaction happens in-place during iteration:
|
||||
// - still_pending tracks the write position (where to put next pending component)
|
||||
// - i tracks the read position (which component we're testing)
|
||||
// - When teardown() returns false, we copy component[i] to component[still_pending]
|
||||
// - When teardown() returns true, we just skip it (don't increment still_pending)
|
||||
//
|
||||
// Example with 4 components where B can teardown immediately:
|
||||
//
|
||||
// Start:
|
||||
// pending_components: [A, B, C, D]
|
||||
// pending_count: 4 ^----------^
|
||||
//
|
||||
// Iteration 1:
|
||||
// i=0: A needs more time → keep at pos 0 (no copy needed)
|
||||
// i=1: B finished → skip
|
||||
// i=2: C needs more time → copy to pos 1
|
||||
// i=3: D needs more time → copy to pos 2
|
||||
//
|
||||
// After iteration 1:
|
||||
// pending_components: [A, C, D | D]
|
||||
// pending_count: 3 ^--------^
|
||||
//
|
||||
// Iteration 2:
|
||||
// i=0: A finished → skip
|
||||
// i=1: C needs more time → copy to pos 0
|
||||
// i=2: D finished → skip
|
||||
//
|
||||
// After iteration 2:
|
||||
// pending_components: [C | C, D, D] (positions 1-3 have old values)
|
||||
// pending_count: 1 ^--^
|
||||
|
||||
while (pending_count > 0 && (now - start_time) < timeout_ms) {
|
||||
// Feed watchdog during teardown to prevent triggering
|
||||
this->feed_wdt(now);
|
||||
|
||||
// Use iterator to safely erase elements
|
||||
for (auto it = pending_components.begin(); it != pending_components.end();) {
|
||||
if ((*it)->teardown()) {
|
||||
// Component finished teardown, erase it
|
||||
it = pending_components.erase(it);
|
||||
} else {
|
||||
// Component still needs time
|
||||
++it;
|
||||
// Process components and compact the array, keeping only those still pending
|
||||
size_t still_pending = 0;
|
||||
for (size_t i = 0; i < pending_count; ++i) {
|
||||
if (!pending_components[i]->teardown()) {
|
||||
// Component still needs time, copy it forward
|
||||
if (still_pending != i) {
|
||||
pending_components[still_pending] = pending_components[i];
|
||||
}
|
||||
++still_pending;
|
||||
}
|
||||
// Component finished teardown, skip it (don't increment still_pending)
|
||||
}
|
||||
pending_count = still_pending;
|
||||
|
||||
// Give some time for I/O operations if components are still pending
|
||||
if (!pending_components.empty()) {
|
||||
if (pending_count > 0) {
|
||||
this->yield_with_select_(1);
|
||||
}
|
||||
|
||||
@@ -287,11 +336,11 @@ void Application::teardown_components(uint32_t timeout_ms) {
|
||||
now = millis();
|
||||
}
|
||||
|
||||
if (!pending_components.empty()) {
|
||||
if (pending_count > 0) {
|
||||
// Note: At this point, connections are either disconnected or in a bad state,
|
||||
// so this warning will only appear via serial rather than being transmitted to clients
|
||||
for (auto *component : pending_components) {
|
||||
ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms", component->get_component_source(),
|
||||
for (size_t i = 0; i < pending_count; ++i) {
|
||||
ESP_LOGW(TAG, "%s did not complete teardown within %" PRIu32 " ms", pending_components[i]->get_component_source(),
|
||||
timeout_ms);
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/scheduler.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
#include "esphome/core/device.h"
|
||||
@@ -248,6 +249,8 @@ class Application {
|
||||
bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; }
|
||||
|
||||
std::string get_compilation_time() const { return this->compilation_time_; }
|
||||
/// Get the compilation time as StringRef (for API usage)
|
||||
StringRef get_compilation_time_ref() const { return StringRef(this->compilation_time_); }
|
||||
|
||||
/// Get the cached time in milliseconds from when the current component started its loop execution
|
||||
inline uint32_t IRAM_ATTR HOT get_loop_component_start_time() const { return this->loop_component_start_time_; }
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -50,13 +51,18 @@ std::string EntityBase::get_object_id() const {
|
||||
if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
|
||||
// `App.get_friendly_name()` is dynamic.
|
||||
return str_sanitize(str_snake_case(App.get_friendly_name()));
|
||||
} else {
|
||||
}
|
||||
// `App.get_friendly_name()` is constant.
|
||||
if (this->object_id_c_str_ == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return this->object_id_c_str_;
|
||||
return this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_;
|
||||
}
|
||||
StringRef EntityBase::get_object_id_ref_for_api_() const {
|
||||
static constexpr auto EMPTY_STRING = StringRef::from_lit("");
|
||||
// Return empty for dynamic case (MAC suffix)
|
||||
if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
// For static case, return the string or empty if null
|
||||
return this->object_id_c_str_ == nullptr ? EMPTY_STRING : StringRef(this->object_id_c_str_);
|
||||
}
|
||||
void EntityBase::set_object_id(const char *object_id) {
|
||||
this->object_id_c_str_ = object_id;
|
||||
|
@@ -12,6 +12,11 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// Forward declaration for friend access
|
||||
namespace api {
|
||||
class APIConnection;
|
||||
} // namespace api
|
||||
|
||||
enum EntityCategory : uint8_t {
|
||||
ENTITY_CATEGORY_NONE = 0,
|
||||
ENTITY_CATEGORY_CONFIG = 1,
|
||||
@@ -81,6 +86,12 @@ class EntityBase {
|
||||
void set_has_state(bool state) { this->flags_.has_state = state; }
|
||||
|
||||
protected:
|
||||
friend class api::APIConnection;
|
||||
|
||||
// Get object_id as StringRef when it's static (for API usage)
|
||||
// Returns empty StringRef if object_id is dynamic (needs allocation)
|
||||
StringRef get_object_id_ref_for_api_() const;
|
||||
|
||||
/// The hash_base() function has been deprecated. It is kept in this
|
||||
/// class for now, to prevent external components from not compiling.
|
||||
virtual uint32_t hash_base() { return 0L; }
|
||||
|
@@ -41,19 +41,30 @@ static const uint16_t CRC16_1021_BE_LUT_H[] = {0x0000, 0x1231, 0x2462, 0x3653, 0
|
||||
|
||||
// Mathematics
|
||||
|
||||
uint8_t crc8(const uint8_t *data, uint8_t len) {
|
||||
uint8_t crc = 0;
|
||||
|
||||
uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc, uint8_t poly, bool msb_first) {
|
||||
while ((len--) != 0u) {
|
||||
uint8_t inbyte = *data++;
|
||||
if (msb_first) {
|
||||
// MSB first processing (for polynomials like 0x31, 0x07)
|
||||
crc ^= inbyte;
|
||||
for (uint8_t i = 8; i != 0u; i--) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ poly;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LSB first processing (default for Dallas/Maxim 0x8C)
|
||||
for (uint8_t i = 8; i != 0u; i--) {
|
||||
bool mix = (crc ^ inbyte) & 0x01;
|
||||
crc >>= 1;
|
||||
if (mix)
|
||||
crc ^= 0x8C;
|
||||
crc ^= poly;
|
||||
inbyte >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
@@ -145,8 +145,8 @@ template<typename T, typename U> T remap(U value, U min, U max, T min_out, T max
|
||||
return (value - min) * (max_out - min_out) / (max - min) + min_out;
|
||||
}
|
||||
|
||||
/// Calculate a CRC-8 checksum of \p data with size \p len using the CRC-8-Dallas/Maxim polynomial.
|
||||
uint8_t crc8(const uint8_t *data, uint8_t len);
|
||||
/// Calculate a CRC-8 checksum of \p data with size \p len.
|
||||
uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc = 0x00, uint8_t poly = 0x8C, bool msb_first = false);
|
||||
|
||||
/// Calculate a CRC-16 checksum of \p data with size \p len.
|
||||
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc = 0xffff, uint16_t reverse_poly = 0xa001,
|
||||
|
@@ -357,6 +357,19 @@ build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:runtime.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32C6
|
||||
build_unflags =
|
||||
${common.build_unflags}
|
||||
|
||||
[env:esp32c6-idf-tidy]
|
||||
extends = common:esp32-idf
|
||||
board = esp32-c6-devkitc-1
|
||||
board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32c6-idf-tidy
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
-DUSE_ESP32_VARIANT_ESP32C6
|
||||
build_unflags =
|
||||
${common.build_unflags}
|
||||
|
||||
;;;;;;;; ESP32-S2 ;;;;;;;;
|
||||
|
||||
|
@@ -15,7 +15,7 @@ esphome-dashboard==20250814.0
|
||||
aioesphomeapi==39.0.0
|
||||
zeroconf==0.147.0
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.18.14 # dashboard_import
|
||||
ruamel.yaml==0.18.15 # dashboard_import
|
||||
esphome-glyphsets==0.2.0
|
||||
pillow==10.4.0
|
||||
cairosvg==2.8.2
|
||||
|
@@ -0,0 +1,14 @@
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
|
||||
api:
|
||||
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- homeassistant.tag_scanned: 'test_tag_123'
|
||||
- homeassistant.tag_scanned:
|
||||
tag: 'another_tag'
|
||||
- homeassistant.tag_scanned:
|
||||
tag: !lambda 'return "dynamic_tag";'
|
17
tests/integration/fixtures/crc8_helper.yaml
Normal file
17
tests/integration/fixtures/crc8_helper.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
esphome:
|
||||
name: crc8-helper-test
|
||||
|
||||
host:
|
||||
|
||||
api:
|
||||
|
||||
logger:
|
||||
level: INFO
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: EXTERNAL_COMPONENT_PATH
|
||||
components: [crc8_test_component]
|
||||
|
||||
crc8_test_component:
|
@@ -0,0 +1,17 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
crc8_test_component_ns = cg.esphome_ns.namespace("crc8_test_component")
|
||||
CRC8TestComponent = crc8_test_component_ns.class_("CRC8TestComponent", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CRC8TestComponent),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
@@ -0,0 +1,170 @@
|
||||
#include "crc8_test_component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace crc8_test_component {
|
||||
|
||||
static const char *const TAG = "crc8_test";
|
||||
|
||||
void CRC8TestComponent::setup() {
|
||||
ESP_LOGI(TAG, "CRC8 Helper Function Integration Test Starting");
|
||||
|
||||
// Run all test suites
|
||||
test_crc8_dallas_maxim();
|
||||
test_crc8_sensirion_style();
|
||||
test_crc8_pec_style();
|
||||
test_crc8_parameter_equivalence();
|
||||
test_crc8_edge_cases();
|
||||
test_component_compatibility();
|
||||
|
||||
ESP_LOGI(TAG, "CRC8 Integration Test Complete");
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_dallas_maxim() {
|
||||
ESP_LOGI(TAG, "Testing Dallas/Maxim CRC8 (default parameters)");
|
||||
|
||||
// Test vectors for Dallas/Maxim CRC8 (polynomial 0x8C, LSB-first, init 0x00)
|
||||
const uint8_t test1[] = {0x01};
|
||||
const uint8_t test2[] = {0xFF};
|
||||
const uint8_t test3[] = {0x12, 0x34};
|
||||
const uint8_t test4[] = {0xAA, 0xBB, 0xCC};
|
||||
const uint8_t test5[] = {0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
|
||||
bool all_passed = true;
|
||||
all_passed &= verify_crc8("Dallas [0x01]", test1, sizeof(test1), 0x5E);
|
||||
all_passed &= verify_crc8("Dallas [0xFF]", test2, sizeof(test2), 0x35);
|
||||
all_passed &= verify_crc8("Dallas [0x12, 0x34]", test3, sizeof(test3), 0xA2);
|
||||
all_passed &= verify_crc8("Dallas [0xAA, 0xBB, 0xCC]", test4, sizeof(test4), 0xD4);
|
||||
all_passed &= verify_crc8("Dallas [0x01...0x05]", test5, sizeof(test5), 0x2A);
|
||||
|
||||
log_test_result("Dallas/Maxim CRC8", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_sensirion_style() {
|
||||
ESP_LOGI(TAG, "Testing Sensirion CRC8 (0x31 poly, MSB-first, init 0xFF)");
|
||||
|
||||
const uint8_t test1[] = {0x00};
|
||||
const uint8_t test2[] = {0x01};
|
||||
const uint8_t test3[] = {0xFF};
|
||||
const uint8_t test4[] = {0x12, 0x34};
|
||||
const uint8_t test5[] = {0xBE, 0xEF};
|
||||
|
||||
bool all_passed = true;
|
||||
all_passed &= verify_crc8("Sensirion [0x00]", test1, sizeof(test1), 0xAC, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0x01]", test2, sizeof(test2), 0x9D, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0xFF]", test3, sizeof(test3), 0x00, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0x12, 0x34]", test4, sizeof(test4), 0x37, 0xFF, 0x31, true);
|
||||
all_passed &= verify_crc8("Sensirion [0xBE, 0xEF]", test5, sizeof(test5), 0x92, 0xFF, 0x31, true);
|
||||
|
||||
log_test_result("Sensirion CRC8", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_pec_style() {
|
||||
ESP_LOGI(TAG, "Testing PEC CRC8 (0x07 poly, MSB-first, init 0x00)");
|
||||
|
||||
const uint8_t test1[] = {0x00};
|
||||
const uint8_t test2[] = {0x01};
|
||||
const uint8_t test3[] = {0xFF};
|
||||
const uint8_t test4[] = {0x12, 0x34};
|
||||
const uint8_t test5[] = {0xAA, 0xBB};
|
||||
|
||||
bool all_passed = true;
|
||||
all_passed &= verify_crc8("PEC [0x00]", test1, sizeof(test1), 0x00, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0x01]", test2, sizeof(test2), 0x07, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0xFF]", test3, sizeof(test3), 0xF3, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0x12, 0x34]", test4, sizeof(test4), 0xF1, 0x00, 0x07, true);
|
||||
all_passed &= verify_crc8("PEC [0xAA, 0xBB]", test5, sizeof(test5), 0xB2, 0x00, 0x07, true);
|
||||
|
||||
log_test_result("PEC CRC8", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_parameter_equivalence() {
|
||||
ESP_LOGI(TAG, "Testing parameter equivalence");
|
||||
|
||||
const uint8_t test_data[] = {0x12, 0x34, 0x56, 0x78};
|
||||
|
||||
// Test that default parameters work as expected
|
||||
uint8_t default_result = crc8(test_data, sizeof(test_data));
|
||||
uint8_t explicit_result = crc8(test_data, sizeof(test_data), 0x00, 0x8C, false);
|
||||
|
||||
bool passed = (default_result == explicit_result);
|
||||
if (!passed) {
|
||||
ESP_LOGE(TAG, "Parameter equivalence FAILED: default=0x%02X, explicit=0x%02X", default_result, explicit_result);
|
||||
}
|
||||
|
||||
log_test_result("Parameter equivalence", passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_crc8_edge_cases() {
|
||||
ESP_LOGI(TAG, "Testing edge cases");
|
||||
|
||||
bool all_passed = true;
|
||||
|
||||
// Empty array test
|
||||
const uint8_t empty[] = {};
|
||||
uint8_t empty_result = crc8(empty, 0);
|
||||
bool empty_passed = (empty_result == 0x00); // Should return init value
|
||||
if (!empty_passed) {
|
||||
ESP_LOGE(TAG, "Empty array test FAILED: expected 0x00, got 0x%02X", empty_result);
|
||||
}
|
||||
all_passed &= empty_passed;
|
||||
|
||||
// Single byte tests
|
||||
const uint8_t single_zero[] = {0x00};
|
||||
const uint8_t single_ff[] = {0xFF};
|
||||
all_passed &= verify_crc8("Single [0x00]", single_zero, 1, 0x00);
|
||||
all_passed &= verify_crc8("Single [0xFF]", single_ff, 1, 0x35);
|
||||
|
||||
log_test_result("Edge cases", all_passed);
|
||||
}
|
||||
|
||||
void CRC8TestComponent::test_component_compatibility() {
|
||||
ESP_LOGI(TAG, "Testing component compatibility");
|
||||
|
||||
// Test specific component use cases
|
||||
bool all_passed = true;
|
||||
|
||||
// AGS10-style data (Sensirion CRC8)
|
||||
const uint8_t ags10_data[] = {0x12, 0x34, 0x56};
|
||||
uint8_t ags10_result = crc8(ags10_data, sizeof(ags10_data), 0xFF, 0x31, true);
|
||||
ESP_LOGI(TAG, "AGS10-style CRC8: 0x%02X", ags10_result);
|
||||
|
||||
// LC709203F-style data (PEC CRC8)
|
||||
const uint8_t lc_data[] = {0xAA, 0xBB};
|
||||
uint8_t lc_result = crc8(lc_data, sizeof(lc_data), 0x00, 0x07, true);
|
||||
ESP_LOGI(TAG, "LC709203F-style CRC8: 0x%02X", lc_result);
|
||||
|
||||
// DallasTemperature-style data (Dallas CRC8)
|
||||
const uint8_t dallas_data[] = {0x28, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC};
|
||||
uint8_t dallas_result = crc8(dallas_data, sizeof(dallas_data));
|
||||
ESP_LOGI(TAG, "Dallas-style CRC8: 0x%02X", dallas_result);
|
||||
|
||||
all_passed = true; // These are just demonstration tests
|
||||
log_test_result("Component compatibility", all_passed);
|
||||
}
|
||||
|
||||
bool CRC8TestComponent::verify_crc8(const char *test_name, const uint8_t *data, uint8_t len, uint8_t expected,
|
||||
uint8_t crc, uint8_t poly, bool msb_first) {
|
||||
uint8_t result = esphome::crc8(data, len, crc, poly, msb_first);
|
||||
bool passed = (result == expected);
|
||||
|
||||
if (passed) {
|
||||
ESP_LOGI(TAG, "%s: PASS (0x%02X)", test_name, result);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: FAIL - expected 0x%02X, got 0x%02X", test_name, expected, result);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
void CRC8TestComponent::log_test_result(const char *test_name, bool passed) {
|
||||
if (passed) {
|
||||
ESP_LOGI(TAG, "%s: ALL TESTS PASSED", test_name);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "%s: SOME TESTS FAILED", test_name);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crc8_test_component
|
||||
} // namespace esphome
|
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace crc8_test_component {
|
||||
|
||||
class CRC8TestComponent : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
private:
|
||||
void test_crc8_dallas_maxim();
|
||||
void test_crc8_sensirion_style();
|
||||
void test_crc8_pec_style();
|
||||
void test_crc8_parameter_equivalence();
|
||||
void test_crc8_edge_cases();
|
||||
void test_component_compatibility();
|
||||
void test_old_vs_new_implementations();
|
||||
|
||||
void log_test_result(const char *test_name, bool passed);
|
||||
bool verify_crc8(const char *test_name, const uint8_t *data, uint8_t len, uint8_t expected, uint8_t crc = 0x00,
|
||||
uint8_t poly = 0x8C, bool msb_first = false);
|
||||
};
|
||||
|
||||
} // namespace crc8_test_component
|
||||
} // namespace esphome
|
100
tests/integration/test_crc8_helper.py
Normal file
100
tests/integration/test_crc8_helper.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""Integration test for CRC8 helper function."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crc8_helper(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test the CRC8 helper function through integration testing."""
|
||||
# Get the path to the external components directory
|
||||
external_components_path = str(
|
||||
Path(__file__).parent / "fixtures" / "external_components"
|
||||
)
|
||||
|
||||
# Replace the placeholder in the YAML config with the actual path
|
||||
yaml_config = yaml_config.replace(
|
||||
"EXTERNAL_COMPONENT_PATH", external_components_path
|
||||
)
|
||||
|
||||
# Track test completion with asyncio.Event
|
||||
test_complete = asyncio.Event()
|
||||
|
||||
# Track test results
|
||||
test_results = {
|
||||
"dallas_maxim": False,
|
||||
"sensirion": False,
|
||||
"pec": False,
|
||||
"parameter_equivalence": False,
|
||||
"edge_cases": False,
|
||||
"component_compatibility": False,
|
||||
"setup_started": False,
|
||||
}
|
||||
|
||||
def on_log_line(line):
|
||||
"""Process log lines to track test progress and results."""
|
||||
# Track test start
|
||||
if "CRC8 Helper Function Integration Test Starting" in line:
|
||||
test_results["setup_started"] = True
|
||||
|
||||
# Track test completion
|
||||
elif "CRC8 Integration Test Complete" in line:
|
||||
test_complete.set()
|
||||
|
||||
# Track individual test results
|
||||
elif "ALL TESTS PASSED" in line:
|
||||
if "Dallas/Maxim CRC8" in line:
|
||||
test_results["dallas_maxim"] = True
|
||||
elif "Sensirion CRC8" in line:
|
||||
test_results["sensirion"] = True
|
||||
elif "PEC CRC8" in line:
|
||||
test_results["pec"] = True
|
||||
elif "Parameter equivalence" in line:
|
||||
test_results["parameter_equivalence"] = True
|
||||
elif "Edge cases" in line:
|
||||
test_results["edge_cases"] = True
|
||||
elif "Component compatibility" in line:
|
||||
test_results["component_compatibility"] = True
|
||||
|
||||
# Log failures for debugging
|
||||
elif "TEST FAILED:" in line or "SUBTEST FAILED:" in line:
|
||||
print(f"CRC8 Test Failure: {line}")
|
||||
|
||||
# Compile and run the test
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=on_log_line),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
# Verify device info
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "crc8-helper-test"
|
||||
|
||||
# Wait for tests to complete with timeout
|
||||
try:
|
||||
await asyncio.wait_for(test_complete.wait(), timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("CRC8 integration test timed out after 5 seconds")
|
||||
|
||||
# Verify all tests passed
|
||||
assert test_results["setup_started"], "CRC8 test setup never started"
|
||||
assert test_results["dallas_maxim"], "Dallas/Maxim CRC8 test failed"
|
||||
assert test_results["sensirion"], "Sensirion CRC8 test failed"
|
||||
assert test_results["pec"], "PEC CRC8 test failed"
|
||||
assert test_results["parameter_equivalence"], (
|
||||
"Parameter equivalence test failed"
|
||||
)
|
||||
assert test_results["edge_cases"], "Edge cases test failed"
|
||||
assert test_results["component_compatibility"], (
|
||||
"Component compatibility test failed"
|
||||
)
|
Reference in New Issue
Block a user