mirror of
https://github.com/esphome/esphome.git
synced 2026-01-20 18:09:10 -07:00
Compare commits
9 Commits
noise_stac
...
mqtt_forma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77b6720a25 | ||
|
|
2f8f052f43 | ||
|
|
86e70c7e76 | ||
|
|
40025bb277 | ||
|
|
438bb96687 | ||
|
|
c1e1325af2 | ||
|
|
944194e04e | ||
|
|
d27d6d64da | ||
|
|
2182d1e9f0 |
@@ -3,7 +3,6 @@
|
||||
#ifdef USE_API_NOISE
|
||||
#include "api_connection.h" // For ClientInfo struct
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -257,30 +256,28 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
if (state_ == State::SERVER_HELLO) {
|
||||
// send server hello
|
||||
constexpr size_t mac_len = 13; // 12 hex chars + null terminator
|
||||
const std::string &name = App.get_name();
|
||||
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||
char mac[mac_len];
|
||||
get_mac_address_into_buffer(mac);
|
||||
|
||||
// Calculate positions and sizes
|
||||
size_t name_len = name.size() + 1; // including null terminator
|
||||
size_t name_offset = 1;
|
||||
size_t mac_offset = name_offset + name_len;
|
||||
size_t total_size = 1 + name_len + MAC_ADDRESS_BUFFER_SIZE;
|
||||
size_t total_size = 1 + name_len + mac_len;
|
||||
|
||||
// 1 (proto) + name (max ESPHOME_DEVICE_NAME_MAX_LEN) + 1 (name null)
|
||||
// + mac (MAC_ADDRESS_BUFFER_SIZE - 1) + 1 (mac null)
|
||||
constexpr size_t max_msg_size = 1 + ESPHOME_DEVICE_NAME_MAX_LEN + 1 + MAC_ADDRESS_BUFFER_SIZE;
|
||||
uint8_t msg[max_msg_size];
|
||||
auto msg = std::make_unique<uint8_t[]>(total_size);
|
||||
|
||||
// chosen proto
|
||||
msg[0] = 0x01;
|
||||
|
||||
// node name, terminated by null byte
|
||||
std::memcpy(msg + name_offset, name.c_str(), name_len);
|
||||
std::memcpy(msg.get() + name_offset, name.c_str(), name_len);
|
||||
// node mac, terminated by null byte
|
||||
std::memcpy(msg + mac_offset, mac, MAC_ADDRESS_BUFFER_SIZE);
|
||||
std::memcpy(msg.get() + mac_offset, mac, mac_len);
|
||||
|
||||
aerr = write_frame_(msg, total_size);
|
||||
aerr = write_frame_(msg.get(), total_size);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
|
||||
@@ -356,32 +353,35 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
return APIError::OK;
|
||||
}
|
||||
void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) {
|
||||
// Max reject message: "Bad handshake packet len" (24) + 1 (failure byte) = 25 bytes
|
||||
uint8_t data[32];
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
// On ESP8266 with flash strings, we need to use PROGMEM-aware functions
|
||||
size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
|
||||
size_t data_size = reason_len + 1;
|
||||
auto data = std::make_unique<uint8_t[]>(data_size);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message from PROGMEM
|
||||
if (reason_len > 0) {
|
||||
memcpy_P(data + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||
memcpy_P(data.get() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
|
||||
}
|
||||
#else
|
||||
// Normal memory access
|
||||
const char *reason_str = LOG_STR_ARG(reason);
|
||||
size_t reason_len = strlen(reason_str);
|
||||
size_t data_size = reason_len + 1;
|
||||
auto data = std::make_unique<uint8_t[]>(data_size);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message in bulk
|
||||
if (reason_len > 0) {
|
||||
// NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary protocol, not a C string
|
||||
std::memcpy(data + 1, reason_str, reason_len);
|
||||
std::memcpy(data.get() + 1, reason_str, reason_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t data_size = reason_len + 1;
|
||||
|
||||
// temporarily remove failed state
|
||||
auto orig_state = state_;
|
||||
state_ = State::EXPLICIT_REJECT;
|
||||
write_frame_(data, data_size);
|
||||
write_frame_(data.get(), data_size);
|
||||
state_ = orig_state;
|
||||
}
|
||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
|
||||
@@ -76,6 +76,7 @@ class CS5460AComponent : public Component,
|
||||
void restart() { restart_(); }
|
||||
|
||||
void setup() override;
|
||||
void loop() override {}
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -28,14 +28,16 @@ const LogString *lock_state_to_string(LockState state) {
|
||||
Lock::Lock() : state(LOCK_STATE_NONE) {}
|
||||
LockCall Lock::make_call() { return LockCall(this); }
|
||||
|
||||
void Lock::set_state_(LockState state) {
|
||||
void Lock::lock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(state);
|
||||
call.set_state(LOCK_STATE_LOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
void Lock::unlock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_UNLOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
|
||||
void Lock::lock() { this->set_state_(LOCK_STATE_LOCKED); }
|
||||
void Lock::unlock() { this->set_state_(LOCK_STATE_UNLOCKED); }
|
||||
void Lock::open() {
|
||||
if (traits.get_supports_open()) {
|
||||
ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str());
|
||||
|
||||
@@ -156,9 +156,6 @@ class Lock : public EntityBase {
|
||||
protected:
|
||||
friend LockCall;
|
||||
|
||||
/// Helper for lock/unlock convenience methods
|
||||
void set_state_(LockState state);
|
||||
|
||||
/** Perform the open latch action with hardware. This method is optional to implement
|
||||
* when creating a new lock.
|
||||
*
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
@@ -44,17 +43,8 @@ template<typename K, typename V> class Mapping {
|
||||
esph_log_e(TAG, "Key '%p' not found in mapping", key);
|
||||
} else if constexpr (std::is_same_v<K, std::string>) {
|
||||
esph_log_e(TAG, "Key '%s' not found in mapping", key.c_str());
|
||||
} else if constexpr (std::is_integral_v<K>) {
|
||||
char buf[24]; // enough for 64-bit integer
|
||||
if constexpr (std::is_unsigned_v<K>) {
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%" PRIu64, static_cast<uint64_t>(key));
|
||||
} else {
|
||||
buf_append_printf(buf, sizeof(buf), 0, "%" PRId64, static_cast<int64_t>(key));
|
||||
}
|
||||
esph_log_e(TAG, "Key '%s' not found in mapping", buf);
|
||||
} else {
|
||||
// All supported key types are handled above - this should never be reached
|
||||
static_assert(sizeof(K) == 0, "Unsupported key type for Mapping error logging");
|
||||
esph_log_e(TAG, "Key '%s' not found in mapping", to_string(key).c_str());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t num
|
||||
}
|
||||
bool CustomMQTTDevice::publish(const std::string &topic, int value) {
|
||||
char buffer[24];
|
||||
int len = snprintf(buffer, sizeof(buffer), "%d", value);
|
||||
size_t len = buf_append_printf(buffer, sizeof(buffer), 0, "%d", value);
|
||||
return global_mqtt_client->publish(topic, buffer, len);
|
||||
}
|
||||
bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) {
|
||||
|
||||
@@ -98,7 +98,17 @@ void MQTTClientComponent::send_device_info_() {
|
||||
uint8_t index = 0;
|
||||
for (auto &ip : network::get_ip_addresses()) {
|
||||
if (ip.is_set()) {
|
||||
root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str();
|
||||
char key[8]; // "ip" + up to 3 digits + null
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
if (index == 0) {
|
||||
key[0] = 'i';
|
||||
key[1] = 'p';
|
||||
key[2] = '\0';
|
||||
} else {
|
||||
buf_append_printf(key, sizeof(key), 0, "ip%u", index);
|
||||
}
|
||||
ip.str_to(ip_buf);
|
||||
root[key] = ip_buf;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
@@ -403,12 +413,6 @@ void MQTTClientComponent::loop() {
|
||||
|
||||
this->last_connected_ = now;
|
||||
this->resubscribe_subscriptions_();
|
||||
|
||||
// Process pending resends for all MQTT components centrally
|
||||
// This is more efficient than each component polling in its own loop
|
||||
for (MQTTComponent *component : this->children_) {
|
||||
component->process_resend();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -190,27 +190,37 @@ bool MQTTComponent::send_discovery_() {
|
||||
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
|
||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||
char friendly_name_hash[9];
|
||||
snprintf(friendly_name_hash, sizeof(friendly_name_hash), "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
||||
buf_append_printf(friendly_name_hash, sizeof(friendly_name_hash), 0, "%08" PRIx32,
|
||||
fnv1_hash(this->friendly_name_()));
|
||||
// Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678")
|
||||
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
|
||||
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
|
||||
char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac_buf);
|
||||
snprintf(unique_id, sizeof(unique_id), "%s-%s-%s", mac_buf, this->component_type(), friendly_name_hash);
|
||||
buf_append_printf(unique_id, sizeof(unique_id), 0, "%s-%s-%s", mac_buf, this->component_type(),
|
||||
friendly_name_hash);
|
||||
root[MQTT_UNIQUE_ID] = unique_id;
|
||||
} else {
|
||||
// default to almost-unique ID. It's a hack but the only way to get that
|
||||
// gorgeous device registry view.
|
||||
root[MQTT_UNIQUE_ID] = "ESP" + std::string(this->component_type()) + object_id.c_str();
|
||||
// "ESP" (3) + component_type (max 20) + object_id (max 128) + null
|
||||
char unique_id_buf[3 + MQTT_COMPONENT_TYPE_MAX_LEN + OBJECT_ID_MAX_LEN + 1];
|
||||
buf_append_printf(unique_id_buf, sizeof(unique_id_buf), 0, "ESP%s%s", this->component_type(),
|
||||
object_id.c_str());
|
||||
root[MQTT_UNIQUE_ID] = unique_id_buf;
|
||||
}
|
||||
|
||||
const std::string &node_name = App.get_name();
|
||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR)
|
||||
root[MQTT_OBJECT_ID] = node_name + "_" + object_id.c_str();
|
||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) {
|
||||
// node_name (max 31) + "_" (1) + object_id (max 128) + null
|
||||
char object_id_full[ESPHOME_DEVICE_NAME_MAX_LEN + 1 + OBJECT_ID_MAX_LEN + 1];
|
||||
buf_append_printf(object_id_full, sizeof(object_id_full), 0, "%s_%s", node_name.c_str(), object_id.c_str());
|
||||
root[MQTT_OBJECT_ID] = object_id_full;
|
||||
}
|
||||
|
||||
const std::string &friendly_name_ref = App.get_friendly_name();
|
||||
const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref;
|
||||
std::string node_area = App.get_area();
|
||||
const char *node_area = App.get_area();
|
||||
|
||||
JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
|
||||
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||
@@ -221,18 +231,29 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION " (ESPHome " ESPHOME_VERSION ")";
|
||||
const char *model = std::strchr(ESPHOME_PROJECT_NAME, '.');
|
||||
device_info[MQTT_DEVICE_MODEL] = model == nullptr ? ESPHOME_BOARD : model + 1;
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] =
|
||||
model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME);
|
||||
if (model == nullptr) {
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = ESPHOME_PROJECT_NAME;
|
||||
} else {
|
||||
// Extract manufacturer (part before '.') using stack buffer to avoid heap allocation
|
||||
// memcpy is used instead of strncpy since we know the exact length and strncpy
|
||||
// would still require manual null-termination
|
||||
char manufacturer[sizeof(ESPHOME_PROJECT_NAME)];
|
||||
size_t len = model - ESPHOME_PROJECT_NAME;
|
||||
memcpy(manufacturer, ESPHOME_PROJECT_NAME, len);
|
||||
manufacturer[len] = '\0';
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = manufacturer;
|
||||
}
|
||||
#else
|
||||
static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")";
|
||||
// Buffer sized for format string expansion: ~4 bytes net growth from format specifier to 8 hex digits, plus
|
||||
// safety margin
|
||||
char version_buf[sizeof(ver_fmt) + 8];
|
||||
#ifdef USE_ESP8266
|
||||
char fmt_buf[sizeof(ver_fmt)];
|
||||
strcpy_P(fmt_buf, ver_fmt);
|
||||
const char *fmt = fmt_buf;
|
||||
snprintf_P(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#else
|
||||
const char *fmt = ver_fmt;
|
||||
snprintf(version_buf, sizeof(version_buf), ver_fmt, App.get_config_hash());
|
||||
#endif
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(fmt, App.get_config_hash());
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
|
||||
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif";
|
||||
@@ -246,7 +267,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "Host";
|
||||
#endif
|
||||
#endif
|
||||
if (!node_area.empty()) {
|
||||
if (node_area[0] != '\0') {
|
||||
device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
|
||||
}
|
||||
|
||||
@@ -308,12 +329,16 @@ void MQTTComponent::call_setup() {
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTComponent::process_resend() {
|
||||
// Called by MQTTClientComponent when connected to process pending resends
|
||||
// Note: is_internal() check not needed - internal components are never registered
|
||||
if (!this->resend_state_)
|
||||
void MQTTComponent::call_loop() {
|
||||
if (this->is_internal())
|
||||
return;
|
||||
|
||||
this->loop();
|
||||
|
||||
if (!this->resend_state_ || !this->is_connected_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->resend_state_ = false;
|
||||
if (this->is_discovery_enabled()) {
|
||||
if (!this->send_discovery_()) {
|
||||
|
||||
@@ -81,6 +81,8 @@ class MQTTComponent : public Component {
|
||||
/// Override setup_ so that we can call send_discovery() when needed.
|
||||
void call_setup() override;
|
||||
|
||||
void call_loop() override;
|
||||
|
||||
void call_dump_config() override;
|
||||
|
||||
/// Send discovery info the Home Assistant, override this.
|
||||
@@ -131,9 +133,6 @@ class MQTTComponent : public Component {
|
||||
/// Internal method for the MQTT client base to schedule a resend of the state on reconnect.
|
||||
void schedule_resend_state();
|
||||
|
||||
/// Process pending resend if needed (called by MQTTClientComponent)
|
||||
void process_resend();
|
||||
|
||||
/** Send a MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
|
||||
@@ -175,7 +175,7 @@ bool MQTTFanComponent::publish_state() {
|
||||
auto traits = this->state_->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
char buf[12];
|
||||
int len = snprintf(buf, sizeof(buf), "%d", this->state_->speed);
|
||||
size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
|
||||
bool success = this->publish(this->get_speed_level_state_topic(), buf, len);
|
||||
failed = failed || !success;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ bool MQTTNumberComponent::send_initial_state() {
|
||||
}
|
||||
bool MQTTNumberComponent::publish_state(float value) {
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "%f", value);
|
||||
buf_append_printf(buffer, sizeof(buffer), 0, "%f", value);
|
||||
return this->publish(this->get_state_topic_(), buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,17 +79,13 @@ async def setup_conf(config, key):
|
||||
|
||||
async def to_code(config):
|
||||
# Request specific WiFi listeners based on which sensors are configured
|
||||
# Each sensor needs its own listener slot - call request for EACH sensor
|
||||
|
||||
# SSID and BSSID use WiFiConnectStateListener
|
||||
for key in (CONF_SSID, CONF_BSSID):
|
||||
if key in config:
|
||||
wifi.request_wifi_connect_state_listener()
|
||||
if CONF_SSID in config or CONF_BSSID in config:
|
||||
wifi.request_wifi_connect_state_listener()
|
||||
|
||||
# IP address and DNS use WiFiIPStateListener
|
||||
for key in (CONF_IP_ADDRESS, CONF_DNS_ADDRESS):
|
||||
if key in config:
|
||||
wifi.request_wifi_ip_state_listener()
|
||||
if CONF_IP_ADDRESS in config or CONF_DNS_ADDRESS in config:
|
||||
wifi.request_wifi_ip_state_listener()
|
||||
|
||||
# Scan results use WiFiScanResultsListener
|
||||
if CONF_SCAN_RESULTS in config:
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace x9c {
|
||||
|
||||
static const char *const TAG = "x9c.output";
|
||||
|
||||
void X9cOutput::trim_value(int32_t change_amount) {
|
||||
void X9cOutput::trim_value(int change_amount) {
|
||||
if (change_amount == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -47,17 +47,17 @@ void X9cOutput::setup() {
|
||||
|
||||
if (this->initial_value_ <= 0.50) {
|
||||
this->trim_value(-101); // Set min value (beyond 0)
|
||||
this->trim_value(lroundf(this->initial_value_ * 100));
|
||||
this->trim_value(static_cast<uint32_t>(roundf(this->initial_value_ * 100)));
|
||||
} else {
|
||||
this->trim_value(101); // Set max value (beyond 100)
|
||||
this->trim_value(lroundf(this->initial_value_ * 100) - 100);
|
||||
this->trim_value(static_cast<uint32_t>(roundf(this->initial_value_ * 100) - 100));
|
||||
}
|
||||
this->pot_value_ = this->initial_value_;
|
||||
this->write_state(this->initial_value_);
|
||||
}
|
||||
|
||||
void X9cOutput::write_state(float state) {
|
||||
this->trim_value(lroundf((state - this->pot_value_) * 100));
|
||||
this->trim_value(static_cast<uint32_t>(roundf((state - this->pot_value_) * 100)));
|
||||
this->pot_value_ = state;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class X9cOutput : public output::FloatOutput, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void trim_value(int32_t change_amount);
|
||||
void trim_value(int change_amount);
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
@@ -348,8 +348,6 @@ template<typename T> class FixedVector {
|
||||
|
||||
size_t size() const { return size_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
size_t capacity() const { return capacity_; }
|
||||
bool full() const { return size_ == capacity_; }
|
||||
|
||||
/// Access element without bounds checking (matches std::vector behavior)
|
||||
/// Caller must ensure index is valid (i < size())
|
||||
|
||||
Reference in New Issue
Block a user