Merge branch 'dev' into api/peel-first-write-iteration

This commit is contained in:
J. Nick Koston
2026-04-01 16:12:46 -10:00
committed by GitHub
179 changed files with 3275 additions and 1587 deletions
+1 -1
View File
@@ -339,7 +339,7 @@ jobs:
echo "binary=$BINARY" >> $GITHUB_OUTPUT
- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@1c8ae4843586d3ba879736b7f6b7b0c990757fab # v4
uses: CodSpeedHQ/action@d872884a306dd4853acf0f584f4b706cf0cc72a2 # v4
with:
run: ${{ steps.build.outputs.binary }}
mode: simulation
+2
View File
@@ -217,6 +217,7 @@ esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/hc8/* @omartijn
esphome/components/hdc2010/* @optimusprimespace @ssieb
esphome/components/hdc2080/* @G-Pereira @jesserockz
esphome/components/hdc302x/* @joshuasing
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
@@ -330,6 +331,7 @@ esphome/components/mipi_dsi/* @clydebarrow
esphome/components/mipi_rgb/* @clydebarrow
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mitsubishi_cn105/* @crnjan
esphome/components/mixer/speaker/* @kahrendt
esphome/components/mlx90393/* @functionpointer
esphome/components/mlx90614/* @jesserockz
+7 -3
View File
@@ -2,6 +2,7 @@
#include "adc_sensor.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace adc {
@@ -346,7 +347,8 @@ float ADCSensor::sample_autorange_() {
ESP_LOGVV(TAG, "Autorange summary:");
ESP_LOGVV(TAG, " Raw readings: 12db=%d, 6db=%d, 2.5db=%d, 0db=%d", raw12, raw6, raw2, raw0);
ESP_LOGVV(TAG, " Voltages: 12db=%.6f, 6db=%.6f, 2.5db=%.6f, 0db=%.6f", mv12, mv6, mv2, mv0);
ESP_LOGVV(TAG, " Coefficients: c12=%u, c6=%u, c2=%u, c0=%u, sum=%u", c12, c6, c2, c0, csum);
ESP_LOGVV(TAG, " Coefficients: c12=%" PRIu32 ", c6=%" PRIu32 ", c2=%" PRIu32 ", c0=%" PRIu32 ", sum=%" PRIu32, c12,
c6, c2, c0, csum);
if (csum == 0) {
ESP_LOGE(TAG, "Invalid weight sum in autorange calculation");
@@ -354,8 +356,10 @@ float ADCSensor::sample_autorange_() {
}
const float final_result = (mv12 * c12 + mv6 * c6 + mv2 * c2 + mv0 * c0) / csum;
ESP_LOGV(TAG, "Autorange final: (%.6f*%u + %.6f*%u + %.6f*%u + %.6f*%u)/%u = %.6fV", mv12, c12, mv6, c6, mv2, c2, mv0,
c0, csum, final_result);
ESP_LOGV(TAG,
"Autorange final: (%.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 " + %.6f*%" PRIu32 ")/%" PRIu32
" = %.6fV",
mv12, c12, mv6, c6, mv2, c2, mv0, c0, csum, final_result);
return final_result;
}
+6 -6
View File
@@ -1465,7 +1465,7 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent
void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range (max %u)", msg.instance,
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range (max %" PRIu32 ")", msg.instance,
static_cast<uint32_t>(proxies.size()));
return;
}
@@ -1476,7 +1476,7 @@ void APIConnection::on_serial_proxy_configure_request(const SerialProxyConfigure
void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
return;
}
proxies[msg.instance]->write_from_client(msg.data, msg.data_len);
@@ -1485,7 +1485,7 @@ void APIConnection::on_serial_proxy_write_request(const SerialProxyWriteRequest
void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
return;
}
proxies[msg.instance]->set_modem_pins(msg.line_states);
@@ -1494,7 +1494,7 @@ void APIConnection::on_serial_proxy_set_modem_pins_request(const SerialProxySetM
void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
return;
}
SerialProxyGetModemPinsResponse resp{};
@@ -1506,7 +1506,7 @@ void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetM
void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %u out of range", msg.instance);
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
return;
}
switch (msg.type) {
@@ -1536,7 +1536,7 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
break;
}
default:
ESP_LOGW(TAG, "Unknown serial proxy request type: %u", static_cast<uint32_t>(msg.type));
ESP_LOGW(TAG, "Unknown serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
break;
}
}
+12
View File
@@ -44,10 +44,22 @@ static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= AP
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
#ifdef USE_BENCHMARK
class APIConnection;
void bench_enable_immediate_send(APIConnection *conn);
void bench_clear_batch(APIConnection *conn);
void bench_process_batch(APIConnection *conn);
#endif
class APIConnection final : public APIServerConnectionBase {
public:
friend class APIServer;
friend class ListEntitiesIterator;
#ifdef USE_BENCHMARK
friend void bench_enable_immediate_send(APIConnection *conn);
friend void bench_clear_batch(APIConnection *conn);
friend void bench_process_batch(APIConnection *conn);
#endif
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
~APIConnection();
+7 -1
View File
@@ -257,7 +257,13 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
return;
}
uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
uint32_t val;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
// Protobuf fixed32 is little-endian — direct load on LE platforms
memcpy(&val, ptr, 4);
#else
val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
#endif
if (!this->decode_32bit(field_id, Proto32Bit(val))) {
ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
}
@@ -4,6 +4,7 @@
#include "esphome/components/audio/audio_decoder.h"
#include <cinttypes>
#include <cstring>
namespace esphome::audio_file {
@@ -249,7 +250,7 @@ void AudioFileMediaSource::decode_task(void *params) {
audio::AudioStreamInfo stream_info = decoder->get_audio_stream_info().value();
ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %d", stream_info.get_bits_per_sample(),
ESP_LOGD(TAG, "Bits per sample: %d, Channels: %d, Sample rate: %" PRIu32, stream_info.get_bits_per_sample(),
stream_info.get_channels(), stream_info.get_sample_rate());
if (stream_info.get_bits_per_sample() != 16 || stream_info.get_channels() > 2) {
@@ -30,6 +30,19 @@ void BluetoothProxy::setup() {
this->configured_scan_active_ = this->parent_->get_scan_active();
this->parent_->add_scanner_state_listener(this);
this->set_interval(100, [this]() {
if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) {
this->flush_pending_advertisements_();
return;
}
for (uint8_t i = 0; i < this->connection_count_; i++) {
auto *connection = this->connections_[i];
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
connection->disconnect();
}
}
});
}
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
@@ -101,25 +114,15 @@ bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results,
// Flush if we have reached BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE
if (this->response_.advertisements_len >= BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE) {
this->flush_pending_advertisements();
this->flush_pending_advertisements_();
}
}
return true;
}
void BluetoothProxy::flush_pending_advertisements() {
if (this->response_.advertisements_len == 0 || !api::global_api_server->is_connected() ||
this->api_connection_ == nullptr)
return;
// Send the message
this->api_connection_->send_message(this->response_);
void BluetoothProxy::log_advertisement_flush_() {
ESP_LOGV(TAG, "Sent batch of %u BLE advertisements", this->response_.advertisements_len);
// Reset the length for the next batch
this->response_.advertisements_len = 0;
}
void BluetoothProxy::dump_config() {
@@ -130,27 +133,6 @@ void BluetoothProxy::dump_config() {
YESNO(this->active_), this->connection_count_);
}
void BluetoothProxy::loop() {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
for (uint8_t i = 0; i < this->connection_count_; i++) {
auto *connection = this->connections_[i];
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
connection->disconnect();
}
}
return;
}
// Flush any pending BLE advertisements that have been accumulated but not yet sent
uint32_t now = App.get_loop_component_start_time();
// Flush accumulated advertisements every 100ms
if (now - this->last_advertisement_flush_time_ >= 100) {
this->flush_pending_advertisements();
this->last_advertisement_flush_time_ = now;
}
}
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
}
@@ -65,8 +65,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override;
void dump_config() override;
void setup() override;
void loop() override;
void flush_pending_advertisements();
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) {
@@ -150,6 +148,18 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
protected:
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
/// Caller must ensure api_connection_ is non-null and API server is connected.
void flush_pending_advertisements_() {
if (this->response_.advertisements_len == 0)
return;
this->api_connection_->send_message(this->response_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
this->log_advertisement_flush_();
#endif
this->response_.advertisements_len = 0;
}
void log_advertisement_flush_();
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
void log_connection_request_ignored_(BluetoothConnection *connection, espbt::ClientState state);
void log_connection_info_(BluetoothConnection *connection, const char *message);
@@ -166,9 +176,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
// BLE advertisement batching
api::BluetoothLERawAdvertisementsResponse response_;
// Group 3: 4-byte types
uint32_t last_advertisement_flush_time_{0};
// Pre-allocated response message - always ready to send
api::BluetoothConnectionsFreeResponse connections_free_response_;
+5 -2
View File
@@ -1,4 +1,7 @@
#include "bm8563.h"
#include <cinttypes>
#include "esphome/core/log.h"
namespace esphome::bm8563 {
@@ -146,10 +149,10 @@ optional<uint8_t> BM8563::read_register_(uint8_t reg) {
}
void BM8563::set_timer_irq_(uint32_t duration_s) {
ESP_LOGI(TAG, "Timer Duration: %u s", duration_s);
ESP_LOGI(TAG, "Timer Duration: %" PRIu32 " s", duration_s);
if (duration_s > MAX_TIMER_DURATION_S) {
ESP_LOGW(TAG, "Timer duration %u s exceeds maximum %u s", duration_s, MAX_TIMER_DURATION_S);
ESP_LOGW(TAG, "Timer duration %" PRIu32 " s exceeds maximum %" PRIu32 " s", duration_s, MAX_TIMER_DURATION_S);
return;
}
@@ -6,10 +6,7 @@
#ifdef USE_BSEC2
#include "bme68x_bsec2.h"
#include <string>
namespace esphome {
namespace bme68x_bsec2 {
namespace esphome::bme68x_bsec2 {
#define BME68X_BSEC2_ALGORITHM_OUTPUT_LOG(a) (a == ALGORITHM_OUTPUT_CLASSIFICATION ? "Classification" : "Regression")
#define BME68X_BSEC2_OPERATING_AGE_LOG(o) (o == OPERATING_AGE_4D ? "4 days" : "28 days")
@@ -18,9 +15,19 @@ namespace bme68x_bsec2 {
static const char *const TAG = "bme68x_bsec2.sensor";
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
static constexpr const char *const IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
static bool is_no_new_data_warning(int8_t status) {
#ifdef BME68X_W_NO_NEW_DATA
return status == BME68X_W_NO_NEW_DATA;
#else
return status == 2;
#endif
}
void BME68xBSEC2Component::setup() {
this->warn_if_blocking_over_ = 60; // initial reads may block for up to 60ms
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
if (this->bsec_status_ != BSEC_OK) {
this->mark_failed();
@@ -82,7 +89,7 @@ void BME68xBSEC2Component::dump_config() {
" Operating age: %s\n"
" Sample rate: %s\n"
" Voltage: %s\n"
" State save interval: %ims\n"
" State save interval: %" PRIu32 "ms\n"
" Temperature offset: %.2f",
BME68X_BSEC2_OPERATING_AGE_LOG(this->operating_age_), BME68X_BSEC2_SAMPLE_RATE_LOG(this->sample_rate_),
BME68X_BSEC2_VOLTAGE_LOG(this->voltage_), this->state_save_interval_ms_, this->temperature_offset_);
@@ -114,7 +121,8 @@ void BME68xBSEC2Component::loop() {
} else {
this->status_clear_error();
}
if (this->bsec_status_ > BSEC_OK || this->bme68x_status_ > BME68X_OK) {
const bool has_bme68x_warning = this->bme68x_status_ > BME68X_OK && !is_no_new_data_warning(this->bme68x_status_);
if (this->bsec_status_ > BSEC_OK || has_bme68x_warning) {
this->status_set_warning();
} else {
this->status_clear_warning();
@@ -130,7 +138,7 @@ void BME68xBSEC2Component::loop() {
void BME68xBSEC2Component::set_config_(const uint8_t *config, uint32_t len) {
if (len > BSEC_MAX_PROPERTY_BLOB_SIZE) {
ESP_LOGE(TAG, "Configuration is larger than BSEC_MAX_PROPERTY_BLOB_SIZE");
ESP_LOGE(TAG, "Configuration blob too large");
this->mark_failed();
return;
}
@@ -212,14 +220,12 @@ void BME68xBSEC2Component::run_() {
if (curr_time_ns < this->bsec_settings_.next_call) {
return;
}
uint8_t status;
ESP_LOGV(TAG, "Performing sensor run");
struct bme68x_conf bme68x_conf;
this->bsec_status_ = bsec_sensor_control_m(&this->bsec_instance_, curr_time_ns, &this->bsec_settings_);
if (this->bsec_status_ < BSEC_OK) {
ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC2 error code %d)", this->bsec_status_);
ESP_LOGW(TAG, "Fetching control settings failed (BSEC2 error code %d)", this->bsec_status_);
return;
}
@@ -235,9 +241,9 @@ void BME68xBSEC2Component::run_() {
this->bme68x_heatr_conf_.heatr_temp = this->bsec_settings_.heater_temperature;
this->bme68x_heatr_conf_.heatr_dur = this->bsec_settings_.heater_duration;
// status = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
status = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
// this->bme68x_status_ = bme68x_set_op_mode(this->bsec_settings_.op_mode, &this->bme68x_);
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_FORCED_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
this->bme68x_status_ = bme68x_set_op_mode(BME68X_FORCED_MODE, &this->bme68x_);
this->op_mode_ = BME68X_FORCED_MODE;
ESP_LOGV(TAG, "Using forced mode");
@@ -259,9 +265,8 @@ void BME68xBSEC2Component::run_() {
BSEC_TOTAL_HEAT_DUR -
(bme68x_get_meas_dur(BME68X_PARALLEL_MODE, &bme68x_conf, &this->bme68x_) / INT64_C(1000));
status = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
status = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
this->bme68x_status_ = bme68x_set_heatr_conf(BME68X_PARALLEL_MODE, &this->bme68x_heatr_conf_, &this->bme68x_);
this->bme68x_status_ = bme68x_set_op_mode(BME68X_PARALLEL_MODE, &this->bme68x_);
this->op_mode_ = BME68X_PARALLEL_MODE;
ESP_LOGV(TAG, "Using parallel mode");
}
@@ -278,28 +283,19 @@ void BME68xBSEC2Component::run_() {
if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) {
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
uint32_t meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);
ESP_LOGV(TAG, "Queueing read in %" PRIu32 "us", meas_dur);
this->trigger_time_ns_ = curr_time_ns;
this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); });
} else {
ESP_LOGV(TAG, "Measurement not required");
this->read_(curr_time_ns);
ESP_LOGV(TAG, "Measurement not required, queueing immediate read");
this->trigger_time_ns_ = curr_time_ns;
this->set_timeout("read", 0, [this]() { this->read_(this->trigger_time_ns_); });
}
}
void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
ESP_LOGV(TAG, "Reading data");
if (this->bsec_settings_.trigger_measurement) {
uint8_t current_op_mode;
this->bme68x_status_ = bme68x_get_op_mode(&current_op_mode, &this->bme68x_);
if (current_op_mode == BME68X_SLEEP_MODE) {
ESP_LOGV(TAG, "Still in sleep mode, doing nothing");
return;
}
}
if (!this->bsec_settings_.process_data) {
ESP_LOGV(TAG, "Data processing not required");
return;
@@ -309,12 +305,16 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
uint8_t nFields = 0;
this->bme68x_status_ = bme68x_get_data(this->op_mode_, &data[0], &nFields, &this->bme68x_);
if (is_no_new_data_warning(this->bme68x_status_)) {
ESP_LOGV(TAG, "BME68X did not provide new data");
return;
}
if (this->bme68x_status_ != BME68X_OK) {
ESP_LOGW(TAG, "Failed to get sensor data (BME68X error code %d)", this->bme68x_status_);
ESP_LOGW(TAG, "Fetching data failed (BME68X error code %d)", this->bme68x_status_);
return;
}
if (nFields < 1) {
ESP_LOGD(TAG, "BME68X did not provide new data");
ESP_LOGV(TAG, "BME68X did not provide new fields");
return;
}
@@ -373,7 +373,7 @@ void BME68xBSEC2Component::read_(int64_t trigger_time_ns) {
uint8_t num_outputs = BSEC_NUMBER_OUTPUTS;
this->bsec_status_ = bsec_do_steps_m(&this->bsec_instance_, inputs, num_inputs, outputs, &num_outputs);
if (this->bsec_status_ != BSEC_OK) {
ESP_LOGW(TAG, "BSEC2 failed to process signals (BSEC2 error code %d)", this->bsec_status_);
ESP_LOGW(TAG, "Signal processing failed (BSEC2 error code %d)", this->bsec_status_);
return;
}
if (num_outputs < 1) {
@@ -474,7 +474,7 @@ void BME68xBSEC2Component::publish_sensor_(sensor::Sensor *sensor, float value,
#endif
#ifdef USE_TEXT_SENSOR
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
void BME68xBSEC2Component::publish_sensor_(text_sensor::TextSensor *sensor, const char *value) {
if (!sensor || (sensor->has_state() && sensor->state == value)) {
return;
}
@@ -526,6 +526,5 @@ void BME68xBSEC2Component::save_state_(uint8_t accuracy) {
ESP_LOGI(TAG, "Saved state");
}
} // namespace bme68x_bsec2
} // namespace esphome
} // namespace esphome::bme68x_bsec2
#endif
+29 -33
View File
@@ -19,8 +19,7 @@
#include <bsec2.h>
namespace esphome {
namespace bme68x_bsec2 {
namespace esphome::bme68x_bsec2 {
enum AlgorithmOutput {
ALGORITHM_OUTPUT_IAQ,
@@ -97,7 +96,7 @@ class BME68xBSEC2Component : public Component {
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
#endif
#ifdef USE_TEXT_SENSOR
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
void publish_sensor_(text_sensor::TextSensor *sensor, const char *value);
#endif
void load_state_();
@@ -108,39 +107,12 @@ class BME68xBSEC2Component : public Component {
struct bme68x_dev bme68x_;
bsec_bme_settings_t bsec_settings_;
bsec_version_t version_;
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
struct bme68x_heatr_conf bme68x_heatr_conf_;
uint8_t op_mode_; // operating mode of sensor
bsec_library_return_t bsec_status_{BSEC_OK};
int8_t bme68x_status_{BME68X_OK};
int64_t last_time_ms_{0};
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
// toolchains with small std::function SBO
uint32_t millis_overflow_counter_{0};
std::queue<std::function<void()>> queue_;
ESPPreferenceObject bsec_state_;
uint8_t const *bsec2_configuration_{nullptr};
uint32_t bsec2_configuration_length_{0};
bool bsec2_blob_configured_{false};
ESPPreferenceObject bsec_state_;
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_ = 0;
float temperature_offset_{0};
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
OperatingAge operating_age_{OPERATING_AGE_28D};
Voltage voltage_{VOLTAGE_3_3V};
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
#ifdef USE_SENSOR
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
@@ -155,8 +127,32 @@ class BME68xBSEC2Component : public Component {
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *iaq_accuracy_text_sensor_{nullptr};
#endif
int64_t last_time_ms_{0};
int64_t trigger_time_ns_{0}; // Stored for set_timeout lambda to help avoid heap allocation on supported 32-bit
// toolchains with small std::function SBO
uint32_t state_save_interval_ms_{21600000}; // 6 hours - 4 times a day
uint32_t last_state_save_ms_{0};
uint32_t millis_overflow_counter_{0};
uint32_t bsec2_configuration_length_{0};
bsec_library_return_t bsec_status_{BSEC_OK};
float temperature_offset_{0};
AlgorithmOutput algorithm_output_{ALGORITHM_OUTPUT_IAQ};
OperatingAge operating_age_{OPERATING_AGE_28D};
Voltage voltage_{VOLTAGE_3_3V};
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate pressure_sample_rate_{SAMPLE_RATE_DEFAULT};
SampleRate humidity_sample_rate_{SAMPLE_RATE_DEFAULT};
uint8_t bsec_instance_[BSEC_INSTANCE_SIZE];
uint8_t op_mode_; // operating mode of sensor
int8_t bme68x_status_{BME68X_OK};
bool bsec2_blob_configured_{false};
};
} // namespace bme68x_bsec2
} // namespace esphome
} // namespace esphome::bme68x_bsec2
#endif
@@ -126,7 +126,7 @@ void BMP581Component::setup() {
}
// verify id
if (chip_id != BMP581_ASIC_ID) {
if (chip_id != BMP581_ASIC_ID && chip_id != BMP585_ASIC_ID) {
ESP_LOGE(TAG, "Unknown chip ID");
this->error_code_ = ERROR_WRONG_CHIP_ID;
+2 -1
View File
@@ -8,7 +8,8 @@
namespace esphome::bmp581_base {
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
static const uint8_t BMP585_ASIC_ID = 0x51;
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
// BMP581 Register Addresses
enum {
@@ -50,7 +50,7 @@ async def to_code(config: ConfigType) -> None:
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
add_idf_component(name="espressif/esp32-camera", ref="2.1.5")
add_idf_component(name="espressif/esp32-camera", ref="2.1.6")
cg.add_define("USE_ESP32_CAMERA_JPEG_ENCODER")
var = cg.new_Pvariable(
config[CONF_ID],
+46 -1
View File
@@ -91,6 +91,49 @@ void DebugComponent::log_partition_info_() {
flash_area_foreach(fa_cb, nullptr);
}
#ifdef ESPHOME_LOG_HAS_VERBOSE
// Check if an nRF peripheral's ENABLE register indicates it is enabled.
// periph: peripheral register prefix (e.g. USBD, UARTE, SPI)
// reg: register block pointer (e.g. NRF_USBD, NRF_UARTE0)
#define NRF_PERIPH_ENABLED(periph, reg) \
YESNO(((reg)->ENABLE & periph##_ENABLE_ENABLE_Msk) == (periph##_ENABLE_ENABLE_Enabled << periph##_ENABLE_ENABLE_Pos))
static void log_peripherals_info() {
// most peripherals are enabled only when in use so ESP_LOGV is enough
ESP_LOGV(TAG, "Peripherals status:");
ESP_LOGV(TAG, " USBD: %-3s| UARTE0: %-3s| UARTE1: %-3s| UART0: %-3s", //
NRF_PERIPH_ENABLED(USBD, NRF_USBD), NRF_PERIPH_ENABLED(UARTE, NRF_UARTE0),
NRF_PERIPH_ENABLED(UARTE, NRF_UARTE1), NRF_PERIPH_ENABLED(UART, NRF_UART0));
ESP_LOGV(TAG, " TWIS0: %-3s| TWIS1: %-3s| TWIM0: %-3s| TWIM1: %-3s", //
NRF_PERIPH_ENABLED(TWIS, NRF_TWIS0), NRF_PERIPH_ENABLED(TWIS, NRF_TWIS1),
NRF_PERIPH_ENABLED(TWIM, NRF_TWIM0), NRF_PERIPH_ENABLED(TWIM, NRF_TWIM1));
ESP_LOGV(TAG, " TWI0: %-3s| TWI1: %-3s| COMP: %-3s| CCM: %-3s", //
NRF_PERIPH_ENABLED(TWI, NRF_TWI0), NRF_PERIPH_ENABLED(TWI, NRF_TWI1), NRF_PERIPH_ENABLED(COMP, NRF_COMP),
NRF_PERIPH_ENABLED(CCM, NRF_CCM));
ESP_LOGV(TAG, " PDM: %-3s| SPIS0: %-3s| SPIS1: %-3s| SPIS2: %-3s", //
NRF_PERIPH_ENABLED(PDM, NRF_PDM), NRF_PERIPH_ENABLED(SPIS, NRF_SPIS0), NRF_PERIPH_ENABLED(SPIS, NRF_SPIS1),
NRF_PERIPH_ENABLED(SPIS, NRF_SPIS2));
ESP_LOGV(TAG, " SPIM0: %-3s| SPIM1: %-3s| SPIM2: %-3s| SPIM3: %-3s", //
NRF_PERIPH_ENABLED(SPIM, NRF_SPIM0), NRF_PERIPH_ENABLED(SPIM, NRF_SPIM1),
NRF_PERIPH_ENABLED(SPIM, NRF_SPIM2), NRF_PERIPH_ENABLED(SPIM, NRF_SPIM3));
ESP_LOGV(TAG, " SPI0: %-3s| SPI1: %-3s| SPI2: %-3s| SAADC: %-3s", //
NRF_PERIPH_ENABLED(SPI, NRF_SPI0), NRF_PERIPH_ENABLED(SPI, NRF_SPI1), NRF_PERIPH_ENABLED(SPI, NRF_SPI2),
NRF_PERIPH_ENABLED(SAADC, NRF_SAADC));
ESP_LOGV(TAG, " QSPI: %-3s| QDEC: %-3s| LPCOMP: %-3s| I2S: %-3s", //
NRF_PERIPH_ENABLED(QSPI, NRF_QSPI), NRF_PERIPH_ENABLED(QDEC, NRF_QDEC),
NRF_PERIPH_ENABLED(LPCOMP, NRF_LPCOMP), NRF_PERIPH_ENABLED(I2S, NRF_I2S));
ESP_LOGV(TAG, " PWM0: %-3s| PWM1: %-3s| PWM2: %-3s| PWM3: %-3s", //
NRF_PERIPH_ENABLED(PWM, NRF_PWM0), NRF_PERIPH_ENABLED(PWM, NRF_PWM1), NRF_PERIPH_ENABLED(PWM, NRF_PWM2),
NRF_PERIPH_ENABLED(PWM, NRF_PWM3));
ESP_LOGV(TAG, " AAR: %-3s| QSPI deep power-down:%-3s| CRYPTOCELL: %-3s", NRF_PERIPH_ENABLED(AAR, NRF_AAR),
YESNO((NRF_QSPI->IFCONFIG0 & QSPI_IFCONFIG0_DPMENABLE_Msk) ==
(QSPI_IFCONFIG0_DPMENABLE_Enable << QSPI_IFCONFIG0_DPMENABLE_Pos)),
YESNO((NRF_CRYPTOCELL->ENABLE & CRYPTOCELL_ENABLE_ENABLE_Msk) ==
(CRYPTOCELL_ENABLE_ENABLE_Enabled << CRYPTOCELL_ENABLE_ENABLE_Pos)));
}
#undef NRF_PERIPH_ENABLED
#endif
static const char *regout0_to_str(uint32_t value) {
switch (value) {
case (UICR_REGOUT0_VOUT_DEFAULT):
@@ -354,7 +397,9 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
};
ESP_LOGD(TAG, " NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str());
ESP_LOGD(TAG, " NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str());
#ifdef ESPHOME_LOG_HAS_VERBOSE
log_peripherals_info();
#endif
return pos;
}
+11 -17
View File
@@ -2,8 +2,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace dht {
namespace esphome::dht {
static const char *const TAG = "dht";
@@ -45,16 +44,13 @@ void DHT::update() {
}
if (success) {
ESP_LOGD(TAG, "Temperature %.1f°C Humidity %.1f%%", temperature, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(humidity);
this->status_clear_warning();
} else {
ESP_LOGW(TAG, "Invalid readings! Check pin number and pull-up resistor%s.",
this->is_auto_detect_ ? " and try manually specifying the model" : "");
ESP_LOGW(TAG, "Invalid readings");
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(NAN);
if (this->humidity_sensor_ != nullptr)
@@ -73,8 +69,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
*temperature = NAN;
int error_code = 0;
int8_t i = 0;
uint8_t data[5] = {0, 0, 0, 0, 0};
uint8_t data[5] = {};
#ifndef USE_ESP32
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
@@ -107,7 +102,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
uint8_t bit = 7;
uint8_t byte = 0;
for (i = -1; i < 40; i++) {
// On 32-bit Xtensa/RISC-V cores, int8_t would require masking/sign-extension for comparisons
// vs. native int. Using int i is native word size — small win in the timing-critical section.
for (int i = -1; i < 40; i++) {
uint32_t start_time = micros();
// Wait for rising edge
@@ -156,11 +153,9 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
}
}
}
if (!report_errors && error_code != 0)
return false;
if (error_code) {
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
if (error_code != 0) {
if (report_errors)
ESP_LOGW(TAG, ESP_LOG_MSG_COMM_FAIL);
return false;
}
@@ -177,7 +172,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
if (checksum_a != data[4] && checksum_b != data[4]) {
if (report_errors) {
ESP_LOGW(TAG, "Checksum invalid: %u!=%u", checksum_a, data[4]);
ESP_LOGW(TAG, "Invalid checksum");
}
return false;
}
@@ -234,5 +229,4 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
return true;
}
} // namespace dht
} // namespace esphome
} // namespace esphome::dht
+5 -8
View File
@@ -4,10 +4,9 @@
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace dht {
namespace esphome::dht {
enum DHTModel {
enum DHTModel : uint8_t {
DHT_MODEL_AUTO_DETECT = 0,
DHT_MODEL_DHT11,
DHT_MODEL_DHT22,
@@ -42,7 +41,6 @@ class DHT : public PollingComponent {
this->t_pin_ = pin;
this->pin_ = pin->to_isr();
}
void set_model(DHTModel model) { model_ = model; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
@@ -55,13 +53,12 @@ class DHT : public PollingComponent {
protected:
bool read_sensor_(float *temperature, float *humidity, bool report_errors);
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
InternalGPIOPin *t_pin_;
ISRInternalGPIOPin pin_;
DHTModel model_{DHT_MODEL_AUTO_DETECT};
bool is_auto_detect_{false};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
};
} // namespace dht
} // namespace esphome
} // namespace esphome::dht
+3 -1
View File
@@ -1,5 +1,7 @@
#include "dlms_meter.h"
#include <cinttypes>
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
#include <bearssl/bearssl.h>
#elif defined(USE_ESP32)
@@ -21,7 +23,7 @@ void DlmsMeterComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"DLMS Meter:\n"
" Provider: %s\n"
" Read Timeout: %u ms",
" Read Timeout: %" PRIu32 " ms",
provider_name, this->read_timeout_);
#define DLMS_METER_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s##_sensor_);
DLMS_METER_SENSOR_LIST(DLMS_METER_LOG_SENSOR, )
+8 -4
View File
@@ -129,11 +129,15 @@ bool ESP32Preferences::sync() {
}
s_pending_save.clear();
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
if (failed > 0) {
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
last_key);
ESP_LOGE(TAG, "Writing %d items: %d cached, %d written, %d failed. Last error=%s for key=%" PRIu32,
cached + written + failed, cached, written, failed, esp_err_to_name(last_err), last_key);
} else if (written > 0) {
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
} else {
ESP_LOGV(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
}
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
+57 -9
View File
@@ -134,10 +134,38 @@ class HandlerCounts:
_handler_counts = HandlerCounts()
def _add_callback(
parent_var: cg.MockObj,
method: str,
handler_var: cg.MockObj,
params: str,
call_args: str,
) -> None:
"""Generate a lambda callback that forwards to a handler method.
Uses a braced scope with a local pointer variable so the generated C++
lambda captures only that pointer, avoiding GCC warnings about capturing
variables with static storage duration.
"""
cg.add(
cg.RawStatement(
f"{{ auto *h = {handler_var}; "
f"{parent_var}->{method}("
f"[h]({params}) {{ h->{call_args}; }}); }}"
)
)
def register_gap_event_handler(parent_var: cg.MockObj, handler_var: cg.MockObj) -> None:
"""Register a GAP event handler and track the count."""
_handler_counts.gap_event += 1
cg.add(parent_var.register_gap_event_handler(handler_var))
_add_callback(
parent_var,
"add_gap_event_callback",
handler_var,
"esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param",
"gap_event_handler(event, param)",
)
def register_gap_scan_event_handler(
@@ -145,7 +173,13 @@ def register_gap_scan_event_handler(
) -> None:
"""Register a GAP scan event handler and track the count."""
_handler_counts.gap_scan_event += 1
cg.add(parent_var.register_gap_scan_event_handler(handler_var))
_add_callback(
parent_var,
"add_gap_scan_event_callback",
handler_var,
"const esphome::esp32_ble::BLEScanResult &scan_result",
"gap_scan_event_handler(scan_result)",
)
def register_gattc_event_handler(
@@ -153,7 +187,13 @@ def register_gattc_event_handler(
) -> None:
"""Register a GATTc event handler and track the count."""
_handler_counts.gattc_event += 1
cg.add(parent_var.register_gattc_event_handler(handler_var))
_add_callback(
parent_var,
"add_gattc_event_callback",
handler_var,
"esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param",
"gattc_event_handler(event, gattc_if, param)",
)
def register_gatts_event_handler(
@@ -161,7 +201,13 @@ def register_gatts_event_handler(
) -> None:
"""Register a GATTs event handler and track the count."""
_handler_counts.gatts_event += 1
cg.add(parent_var.register_gatts_event_handler(handler_var))
_add_callback(
parent_var,
"add_gatts_event_callback",
handler_var,
"esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param",
"gatts_event_handler(event, gatts_if, param)",
)
def register_ble_status_event_handler(
@@ -169,7 +215,13 @@ def register_ble_status_event_handler(
) -> None:
"""Register a BLE status event handler and track the count."""
_handler_counts.ble_status_event += 1
cg.add(parent_var.register_ble_status_event_handler(handler_var))
_add_callback(
parent_var,
"add_ble_status_event_callback",
handler_var,
"",
"ble_before_disabled_event_handler()",
)
def register_bt_logger(*loggers: BTLoggers) -> None:
@@ -225,10 +277,6 @@ NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
BLEEnabledCondition = esp32_ble_ns.class_("BLEEnabledCondition", automation.Condition)
BLEEnableAction = esp32_ble_ns.class_("BLEEnableAction", automation.Action)
BLEDisableAction = esp32_ble_ns.class_("BLEDisableAction", automation.Action)
+5 -16
View File
@@ -408,9 +408,7 @@ void ESP32BLE::loop() {
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
for (auto *gatts_handler : this->gatts_event_handlers_) {
gatts_handler->gatts_event_handler(event, gatts_if, param);
}
this->gatts_event_callbacks_.call(event, gatts_if, param);
break;
}
#endif
@@ -420,9 +418,7 @@ void ESP32BLE::loop() {
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
for (auto *gattc_handler : this->gattc_event_handlers_) {
gattc_handler->gattc_event_handler(event, gattc_if, param);
}
this->gattc_event_callbacks_.call(event, gattc_if, param);
break;
}
#endif
@@ -431,10 +427,7 @@ void ESP32BLE::loop() {
switch (gap_event) {
case ESP_GAP_BLE_SCAN_RESULT_EVT:
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
// Use the new scan event handler - no memcpy!
for (auto *scan_handler : this->gap_scan_event_handlers_) {
scan_handler->gap_scan_event_handler(ble_event->scan_result());
}
this->gap_scan_event_callbacks_.call(ble_event->scan_result());
#endif
break;
@@ -478,9 +471,7 @@ void ESP32BLE::loop() {
}
// clang-format on
// Dispatch to all registered handlers
for (auto *gap_handler : this->gap_event_handlers_) {
gap_handler->gap_event_handler(gap_event, param);
}
this->gap_event_callbacks_.call(gap_event, param);
}
#endif
break;
@@ -518,9 +509,7 @@ void ESP32BLE::loop_handle_state_transition_not_active_() {
ESP_LOGD(TAG, "Disabling");
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
for (auto *ble_event_handler : this->ble_status_event_handlers_) {
ble_event_handler->ble_before_disabled_event_handler();
}
this->ble_status_event_callbacks_.call();
#endif
if (!ble_dismantle_()) {
+25 -44
View File
@@ -87,37 +87,6 @@ enum BLEComponentState : uint8_t {
BLE_COMPONENT_STATE_ACTIVE,
};
class GAPEventHandler {
public:
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
};
class GAPScanEventHandler {
public:
virtual void gap_scan_event_handler(const BLEScanResult &scan_result) = 0;
};
#ifdef USE_ESP32_BLE_CLIENT
class GATTcEventHandler {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) = 0;
};
#endif
#ifdef USE_ESP32_BLE_SERVER
class GATTsEventHandler {
public:
virtual void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) = 0;
};
#endif
class BLEStatusEventHandler {
public:
virtual void ble_before_disabled_event_handler() = 0;
};
class ESP32BLE : public Component {
public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
@@ -154,22 +123,28 @@ class ESP32BLE : public Component {
#endif
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
template<typename F> void add_gap_event_callback(F &&callback) {
this->gap_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
void register_gap_scan_event_handler(GAPScanEventHandler *handler) {
this->gap_scan_event_handlers_.push_back(handler);
template<typename F> void add_gap_scan_event_callback(F &&callback) {
this->gap_scan_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
template<typename F> void add_gattc_event_callback(F &&callback) {
this->gattc_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); }
template<typename F> void add_gatts_event_callback(F &&callback) {
this->gatts_event_callbacks_.add(std::forward<F>(callback));
}
#endif
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
void register_ble_status_event_handler(BLEStatusEventHandler *handler) {
this->ble_status_event_handlers_.push_back(handler);
template<typename F> void add_ble_status_event_callback(F &&callback) {
this->ble_status_event_callbacks_.add(std::forward<F>(callback));
}
#endif
void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
@@ -202,21 +177,27 @@ class ESP32BLE : public Component {
private:
template<typename... Args> friend void enqueue_ble_event(Args... args);
// Handler vectors - use StaticVector when counts are known at compile time
#ifdef ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT
StaticVector<GAPEventHandler *, ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT> gap_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GAP_EVENT_HANDLER_COUNT,
void(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *)>
gap_event_callbacks_;
#endif
#ifdef ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT
StaticVector<GAPScanEventHandler *, ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT> gap_scan_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GAP_SCAN_EVENT_HANDLER_COUNT, void(const BLEScanResult &)>
gap_scan_event_callbacks_;
#endif
#if defined(USE_ESP32_BLE_CLIENT) && defined(ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT)
StaticVector<GATTcEventHandler *, ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT> gattc_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GATTC_EVENT_HANDLER_COUNT,
void(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *)>
gattc_event_callbacks_;
#endif
#if defined(USE_ESP32_BLE_SERVER) && defined(ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT)
StaticVector<GATTsEventHandler *, ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT> gatts_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_GATTS_EVENT_HANDLER_COUNT,
void(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *)>
gatts_event_callbacks_;
#endif
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
StaticVector<BLEStatusEventHandler *, ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT> ble_status_event_handlers_;
StaticCallbackManager<ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT, void()> ble_status_event_callbacks_;
#endif
// Large objects (size depends on template parameters, but typically aligned to 4 bytes)
@@ -13,7 +13,6 @@ esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon")
ESP32BLEBeacon = esp32_ble_beacon_ns.class_(
"ESP32BLEBeacon",
cg.Component,
esp32_ble.GAPEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
CONF_MAJOR = "major"
@@ -35,7 +35,7 @@ using esp_ble_ibeacon_t = struct {
using namespace esp32_ble;
class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented<ESP32BLE> {
class ESP32BLEBeacon : public Component, public Parented<ESP32BLE> {
public:
explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {}
@@ -51,7 +51,7 @@ class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented
#ifndef CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID
void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; }
#endif
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
protected:
void on_advertise_();
@@ -72,7 +72,6 @@ BLECharacteristic_ns = esp32_ble_server_ns.namespace("BLECharacteristic")
BLEServer = esp32_ble_server_ns.class_(
"BLEServer",
cg.Component,
esp32_ble.GATTsEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
esp32_ble_server_automations_ns = esp32_ble_server_ns.namespace(
@@ -24,7 +24,7 @@ namespace esp32_ble_server {
using namespace esp32_ble;
using namespace bytebuffer;
class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented<ESP32BLE> {
class BLEServer : public Component, public Parented<ESP32BLE> {
public:
void setup() override;
void loop() override;
@@ -53,10 +53,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
const uint16_t *get_clients() const { return this->clients_; }
uint8_t get_client_count() const { return this->client_count_; }
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) override;
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void ble_before_disabled_event_handler() override;
void ble_before_disabled_event_handler();
// Direct callback registration - supports multiple callbacks
void on_connect(std::function<void(uint16_t)> &&callback) {
@@ -90,8 +90,6 @@ esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
ESP32BLETracker = esp32_ble_tracker_ns.class_(
"ESP32BLETracker",
cg.Component,
esp32_ble.GAPEventHandler,
esp32_ble.GATTcEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient")
@@ -88,12 +88,18 @@ void ESP32BLETracker::setup() {
#ifdef USE_OTA_STATE_LISTENER
void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
if (state == ota::OTA_STARTED) {
this->scan_continuous_before_ota_ = this->scan_continuous_;
this->stop_scan();
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
for (auto *client : this->clients_) {
client->disconnect();
}
#endif
} else if ((state == ota::OTA_ERROR || state == ota::OTA_ABORT) && this->scan_continuous_before_ota_) {
this->scan_continuous_before_ota_ = false;
this->scan_continuous_ = true;
// Do not restart scanning immediately here; allow loop() to
// safely restart scanning once the scanner and all clients are idle.
}
}
#endif
@@ -291,10 +291,6 @@ class ESPBTClient : public ESPBTDeviceListener {
};
class ESP32BLETracker : public Component,
public GAPEventHandler,
public GAPScanEventHandler,
public GATTcEventHandler,
public BLEStatusEventHandler,
#ifdef USE_OTA_STATE_LISTENER
public ota::OTAGlobalStateListener,
#endif
@@ -325,11 +321,10 @@ class ESP32BLETracker : public Component,
void start_scan();
void stop_scan();
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void gap_scan_event_handler(const BLEScanResult &scan_result) override;
void ble_before_disabled_event_handler() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
void gap_scan_event_handler(const BLEScanResult &scan_result);
void ble_before_disabled_event_handler();
#ifdef USE_OTA_STATE_LISTENER
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
@@ -436,6 +431,9 @@ class ESP32BLETracker : public Component,
ScannerState scanner_state_{ScannerState::IDLE};
bool scan_continuous_;
bool scan_active_;
#ifdef USE_OTA_STATE_LISTENER
bool scan_continuous_before_ota_{false};
#endif
bool ble_was_disabled_{true};
bool raw_advertisements_{false};
bool parse_advertisements_{false};
+1 -1
View File
@@ -400,7 +400,7 @@ async def to_code(config):
if config[CONF_JPEG_QUALITY] != 0 and config[CONF_PIXEL_FORMAT] != "JPEG":
cg.add_define("USE_ESP32_CAMERA_JPEG_CONVERSION")
add_idf_component(name="espressif/esp32-camera", ref="2.1.5")
add_idf_component(name="espressif/esp32-camera", ref="2.1.6")
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True)
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False)
@@ -448,6 +448,13 @@ void Esp32HostedUpdate::perform(bool force) {
return;
}
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
if (this->firmware_url_.empty()) {
ESP_LOGW(TAG, "No firmware URL available, run check first");
return;
}
#endif
update::UpdateState prev_state = this->state_;
this->state_ = update::UPDATE_STATE_INSTALLING;
this->update_info_.has_progress = false;
@@ -217,7 +217,7 @@ void ESP32TouchComponent::setup() {
for (uint32_t i = 0; i < ONESHOT_SCAN_COUNT; i++) {
err = touch_sensor_trigger_oneshot_scanning(this->sens_handle_, ONESHOT_SCAN_TIMEOUT_MS);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Oneshot scan %d failed: %s", i, esp_err_to_name(err));
ESP_LOGW(TAG, "Oneshot scan %" PRIu32 " failed: %s", i, esp_err_to_name(err));
}
}
@@ -4,6 +4,8 @@
#include "espnow_err.h"
#include <cinttypes>
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
@@ -266,7 +268,7 @@ void ESPNowComponent::loop() {
if (wifi::global_wifi_component != nullptr && wifi::global_wifi_component->is_connected()) {
int32_t new_channel = wifi::global_wifi_component->get_wifi_channel();
if (new_channel != this->wifi_channel_) {
ESP_LOGI(TAG, "Wifi Channel is changed from %d to %d.", this->wifi_channel_, new_channel);
ESP_LOGI(TAG, "Wifi Channel is changed from %d to %" PRId32 ".", this->wifi_channel_, new_channel);
this->wifi_channel_ = new_channel;
}
}
-1
View File
@@ -675,7 +675,6 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
}
out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
control_out_buffer[4] = 0; // This byte should be cleared before setting values
out_data->display_status = this->get_display_state() ? 1 : 0;
this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
out_data->health_mode = this->get_health_mode() ? 1 : 0;
+1
View File
@@ -0,0 +1 @@
CODEOWNERS = ["@G-Pereira", "@jesserockz"]
+71
View File
@@ -0,0 +1,71 @@
#include "hdc2080.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome::hdc2080 {
static const char *const TAG = "hdc2080";
// Register map (Table 8-6)
static constexpr uint8_t REG_TEMPERATURE_LOW = 0x00; // Temperature [7:0]
static constexpr uint8_t REG_TEMPERATURE_HIGH = 0x01; // Temperature [15:8]
static constexpr uint8_t REG_HUMIDITY_LOW = 0x02; // Humidity [7:0]
static constexpr uint8_t REG_HUMIDITY_HIGH = 0x03; // Humidity [15:8]
static constexpr uint8_t REG_RESET_DRDY_INT_CONF = 0x0E; // Soft Reset and Interrupt Configuration
static constexpr uint8_t REG_MEASUREMENT_CONFIGURATION = 0x0F;
// Measurement register (0x0F) bit fields
static constexpr uint8_t MEAS_TRIG = 0x01; // Bit 0: start measurement
static constexpr uint8_t MEAS_CONF_TEMP = 0x02; // Bits 2:1 = 01: temperature only
static constexpr uint8_t MEAS_CONF_HUM = 0x04; // Bits 2:1 = 10: humidity only
void HDC2080Component::setup() {
const uint8_t data = 0x00; // automatic measurement mode disabled, heater off
if (this->write_register(REG_RESET_DRDY_INT_CONF, &data, 1) != i2c::ERROR_OK) {
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
}
void HDC2080Component::dump_config() {
ESP_LOGCONFIG(TAG, "HDC2080:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
}
void HDC2080Component::update() {
uint8_t data = MEAS_TRIG; // 14-bit resolution, measure both, start
if (this->temperature_sensor_ != nullptr && this->humidity_sensor_ == nullptr) {
data = MEAS_TRIG | MEAS_CONF_TEMP;
} else if (this->temperature_sensor_ == nullptr && this->humidity_sensor_ != nullptr) {
data = MEAS_TRIG | MEAS_CONF_HUM;
}
if (this->write_register(REG_MEASUREMENT_CONFIGURATION, &data, 1) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
return;
}
// wait for conversion to complete 2ms should be enough, more is fine
this->set_timeout(5, [this]() {
uint8_t raw_data[4];
if (this->read_register(REG_TEMPERATURE_LOW, raw_data, 4) != i2c::ERROR_OK) {
this->status_set_warning(ESP_LOG_MSG_COMM_FAIL);
return;
}
this->status_clear_warning();
if (this->temperature_sensor_ != nullptr) {
float temp = encode_uint16(raw_data[1], raw_data[0]) * (165.0f / 65536.0f) - 40.5f;
this->temperature_sensor_->publish_state(temp);
}
if (this->humidity_sensor_ != nullptr) {
float humidity = encode_uint16(raw_data[3], raw_data[2]) * (100.0f / 65536.0f);
this->humidity_sensor_->publish_state(humidity);
}
});
}
} // namespace esphome::hdc2080
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome::hdc2080 {
class HDC2080Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_temperature(sensor::Sensor *temperature) { this->temperature_sensor_ = temperature; }
void set_humidity(sensor::Sensor *humidity) { this->humidity_sensor_ = humidity; }
/// Setup the sensor and check for connection.
void setup() override;
void dump_config() override;
void update() override;
protected:
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
};
} // namespace esphome::hdc2080
+57
View File
@@ -0,0 +1,57 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
DEPENDENCIES = ["i2c"]
hdc2080_ns = cg.esphome_ns.namespace("hdc2080")
HDC2080Component = hdc2080_ns.class_(
"HDC2080Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HDC2080Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x40))
.add_extra(cv.has_at_least_one_key(CONF_TEMPERATURE, CONF_HUMIDITY))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature(sens))
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity(sens))
@@ -11,7 +11,7 @@ static const char *const TAG = "http_request";
void HttpRequestComponent::dump_config() {
ESP_LOGCONFIG(TAG,
"HTTP Request:\n"
" Timeout: %ums\n"
" Timeout: %" PRIu32 "ms\n"
" User-Agent: %s\n"
" Follow redirects: %s\n"
" Redirect limit: %d",
@@ -17,6 +17,7 @@
namespace esphome::http_request {
static const char *const TAG = "http_request.idf";
static constexpr uint32_t ERROR_DURATION_MS = 1000;
struct UserData {
const std::vector<std::string> &lower_case_collect_headers;
@@ -57,7 +58,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
const std::vector<Header> &request_headers,
const std::vector<std::string> &lower_case_collect_headers) {
if (!network::is_connected()) {
this->status_momentary_error("failed", 1000);
this->status_momentary_error("failed", ERROR_DURATION_MS);
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
return nullptr;
}
@@ -74,7 +75,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
} else if (method == "PATCH") {
method_idf = HTTP_METHOD_PATCH;
} else {
this->status_momentary_error("failed", 1000);
this->status_momentary_error("failed", ERROR_DURATION_MS);
ESP_LOGE(TAG, "HTTP Request failed; Unsupported method");
return nullptr;
}
@@ -112,6 +113,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
config.event_handler = http_event_handler;
esp_http_client_handle_t client = esp_http_client_init(&config);
if (client == nullptr) {
this->status_momentary_error("failed", ERROR_DURATION_MS);
ESP_LOGE(TAG, "HTTP Request failed; client could not be initialized");
return nullptr;
}
std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
container->set_parent(this);
@@ -129,7 +135,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
esp_err_t err = esp_http_client_open(client, body_len);
if (err != ESP_OK) {
this->status_momentary_error("failed", 1000);
this->status_momentary_error("failed", ERROR_DURATION_MS);
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
return nullptr;
@@ -151,7 +157,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
}
if (err != ESP_OK) {
this->status_momentary_error("failed", 1000);
this->status_momentary_error("failed", ERROR_DURATION_MS);
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
return nullptr;
@@ -176,7 +182,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
err = esp_http_client_set_redirection(client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_http_client_set_redirection failed: %s", esp_err_to_name(err));
this->status_momentary_error("failed", 1000);
this->status_momentary_error("failed", ERROR_DURATION_MS);
esp_http_client_cleanup(client);
return nullptr;
}
@@ -189,7 +195,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_http_client_open failed: %s", esp_err_to_name(err));
this->status_momentary_error("failed", 1000);
this->status_momentary_error("failed", ERROR_DURATION_MS);
esp_http_client_cleanup(client);
return nullptr;
}
@@ -214,7 +220,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
}
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
this->status_momentary_error("failed", 1000);
this->status_momentary_error("failed", ERROR_DURATION_MS);
return container;
}
+3 -1
View File
@@ -1,6 +1,8 @@
#include "hub75_component.h"
#include "esphome/core/application.h"
#include <cinttypes>
#ifdef USE_ESP32
namespace esphome::hub75 {
@@ -58,7 +60,7 @@ void HUB75Display::dump_config() {
config_.pins.oe, config_.pins.clk);
ESP_LOGCONFIG(TAG,
" Clock Speed: %u MHz\n"
" Clock Speed: %" PRIu32 " MHz\n"
" Latch Blanking: %i\n"
" Clock Phase: %s\n"
" Min Refresh Rate: %i Hz\n"
+7 -4
View File
@@ -1,4 +1,7 @@
#include "infrared.h"
#include <cinttypes>
#include "esphome/core/log.h"
#ifdef USE_API
@@ -100,7 +103,7 @@ void Infrared::control(const InfraredCall &call) {
// Zero-copy from packed protobuf data
transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(),
call.get_packed_count());
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%" PRIu16 ", repeat=%" PRIu32, call.get_packed_count(),
call.get_repeat_count());
} else if (call.is_base64url()) {
// Decode base64url (URL-safe) into transmit buffer
@@ -113,16 +116,16 @@ void Infrared::control(const InfraredCall &call) {
for (int32_t timing : transmit_data->get_data()) {
int32_t abs_timing = timing < 0 ? -timing : timing;
if (abs_timing > max_timing_us) {
ESP_LOGE(TAG, "Invalid timing value: %d µs (max %d)", timing, max_timing_us);
ESP_LOGE(TAG, "Invalid timing value: %" PRId32 " µs (max %" PRId32 ")", timing, max_timing_us);
return;
}
}
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%" PRIu32, transmit_data->get_data().size(),
call.get_repeat_count());
} else {
// From vector (lambdas/automations)
transmit_data->set_data(call.get_raw_timings());
ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%u", call.get_raw_timings().size(),
ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%" PRIu32, call.get_raw_timings().size(),
call.get_repeat_count());
}
+18 -16
View File
@@ -3,6 +3,8 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
#include <hal/gpio_hal.h>
namespace esphome {
@@ -193,7 +195,7 @@ void Inkplate::dump_config() {
ESP_LOGCONFIG(TAG,
" Greyscale: %s\n"
" Partial Updating: %s\n"
" Full Update Every: %d",
" Full Update Every: %" PRIu32,
YESNO(this->greyscale_), YESNO(this->partial_updating_), this->full_update_every_);
// Log pins
LOG_PIN(" CKV Pin: ", this->ckv_pin_);
@@ -306,7 +308,7 @@ void Inkplate::fill(Color color) {
// If clipping is active, fall back to base implementation
if (this->get_clipping().is_set()) {
Display::fill(color);
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Fill finished (%" PRIu32 "ms)", millis() - start_time);
return;
}
@@ -329,12 +331,12 @@ void Inkplate::display() {
this->display3b_();
} else {
if (this->partial_updating_ && this->partial_update_()) {
ESP_LOGV(TAG, "Display finished (partial) (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Display finished (partial) (%" PRIu32 "ms)", millis() - start_time);
return;
}
this->display1b_();
}
ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Display finished (full) (%" PRIu32 "ms)", millis() - start_time);
}
void Inkplate::display1b_() {
@@ -409,7 +411,7 @@ void Inkplate::display1b_() {
uint32_t clock = (1UL << this->cl_pin_->get_pin());
uint32_t data_mask = this->get_data_pin_mask_();
ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Display1b start loops (%" PRIu32 "ms)", millis() - start_time);
for (uint8_t k = 0; k < rep; k++) {
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
@@ -440,7 +442,7 @@ void Inkplate::display1b_() {
}
delayMicroseconds(230);
}
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 4, millis() - start_time);
ESP_LOGV(TAG, "Display1b first loop x %d (%" PRIu32 "ms)", 4, millis() - start_time);
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
vscan_start_();
@@ -469,7 +471,7 @@ void Inkplate::display1b_() {
vscan_end_();
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Display1b second loop (%" PRIu32 "ms)", millis() - start_time);
if (this->model_ == INKPLATE_6_PLUS) {
clean_fast_(2, 2);
@@ -495,13 +497,13 @@ void Inkplate::display1b_() {
vscan_end_();
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Display1b third loop (%" PRIu32 "ms)", millis() - start_time);
}
vscan_start_();
eink_off_();
this->block_partial_ = false;
this->partial_updates_ = 0;
ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Display1b finished (%" PRIu32 "ms)", millis() - start_time);
}
void Inkplate::display3b_() {
@@ -614,7 +616,7 @@ void Inkplate::display3b_() {
clean_fast_(3, 1);
vscan_start_();
eink_off_();
ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Display3b finished (%" PRIu32 "ms)", millis() - start_time);
}
bool Inkplate::partial_update_() {
@@ -641,7 +643,7 @@ bool Inkplate::partial_update_() {
this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F];
}
}
ESP_LOGV(TAG, "Partial update buffer built after (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Partial update buffer built after (%" PRIu32 "ms)", millis() - start_time);
int rep = (this->model_ == INKPLATE_6_V2) ? 6 : 5;
@@ -667,7 +669,7 @@ bool Inkplate::partial_update_() {
vscan_end_();
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Partial update loop k=%d (%ums)", k, millis() - start_time);
ESP_LOGV(TAG, "Partial update loop k=%d (%" PRIu32 "ms)", k, millis() - start_time);
}
clean_fast_(2, 2);
clean_fast_(3, 1);
@@ -675,7 +677,7 @@ bool Inkplate::partial_update_() {
eink_off_();
memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_());
ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Partial update finished (%" PRIu32 "ms)", millis() - start_time);
return true;
}
@@ -730,7 +732,7 @@ void Inkplate::clean() {
clean_fast_(0, 8); // Black to Black
clean_fast_(2, 1); // Black to White
clean_fast_(1, 10); // White to White
ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Clean finished (%" PRIu32 "ms)", millis() - start_time);
}
void Inkplate::clean_fast_(uint8_t c, uint8_t rep) {
@@ -773,9 +775,9 @@ void Inkplate::clean_fast_(uint8_t c, uint8_t rep) {
vscan_end_();
}
delayMicroseconds(230);
ESP_LOGV(TAG, "Clean fast rep loop %d finished (%ums)", k, millis() - start_time);
ESP_LOGV(TAG, "Clean fast rep loop %d finished (%" PRIu32 "ms)", k, millis() - start_time);
}
ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time);
ESP_LOGV(TAG, "Clean fast finished (%" PRIu32 "ms)", millis() - start_time);
}
void Inkplate::pins_z_state_() {
@@ -1,18 +1,18 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace internal_temperature {
namespace esphome::internal_temperature {
class InternalTemperatureSensor : public sensor::Sensor, public PollingComponent {
public:
#if defined(USE_ESP32) || (defined(USE_ZEPHYR) && defined(USE_NRF52))
void setup() override;
#endif // USE_ESP32 || (USE_ZEPHYR && USE_NRF52)
void dump_config() override;
void update() override;
};
} // namespace internal_temperature
} // namespace esphome
} // namespace esphome::internal_temperature
@@ -0,0 +1,41 @@
#ifdef USE_BK72XX
#include "esphome/core/log.h"
#include "internal_temperature.h"
extern "C" {
uint32_t temp_single_get_current_temperature(uint32_t *temp_value);
}
namespace esphome::internal_temperature {
static const char *const TAG = "internal_temperature.bk72xx";
void InternalTemperatureSensor::update() {
float temperature = NAN;
bool success = false;
uint32_t raw, result;
result = temp_single_get_current_temperature(&raw);
success = (result == 0);
#if defined(USE_LIBRETINY_VARIANT_BK7231N)
temperature = raw * -0.38f + 156.0f;
#elif defined(USE_LIBRETINY_VARIANT_BK7231T)
temperature = raw * 0.04f;
#else // USE_LIBRETINY_VARIANT
temperature = raw * 0.128f;
#endif // USE_LIBRETINY_VARIANT
if (success && std::isfinite(temperature)) {
this->publish_state(temperature);
} else {
ESP_LOGD(TAG, "Ignoring invalid temperature (success=%d, value=%.1f)", success, temperature);
if (!this->has_state()) {
this->publish_state(NAN);
}
}
}
} // namespace esphome::internal_temperature
#endif // USE_BK72XX
@@ -0,0 +1,10 @@
#include "esphome/core/log.h"
#include "internal_temperature.h"
namespace esphome::internal_temperature {
static const char *const TAG = "internal_temperature";
void InternalTemperatureSensor::dump_config() { LOG_SENSOR("", "Internal Temperature Sensor", this); }
} // namespace esphome::internal_temperature
@@ -1,7 +1,8 @@
#include "internal_temperature.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#include "esphome/core/log.h"
#include "internal_temperature.h"
#if defined(USE_ESP32_VARIANT_ESP32)
// there is no official API available on the original ESP32
extern "C" {
@@ -13,32 +14,20 @@ uint8_t temprature_sens_read();
defined(USE_ESP32_VARIANT_ESP32S3)
#include "driver/temperature_sensor.h"
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
#ifdef USE_RP2040
#include "Arduino.h"
#endif // USE_RP2040
#ifdef USE_BK72XX
extern "C" {
uint32_t temp_single_get_current_temperature(uint32_t *temp_value);
}
#endif // USE_BK72XX
namespace esphome {
namespace internal_temperature {
namespace esphome::internal_temperature {
static const char *const TAG = "internal_temperature.esp32";
static const char *const TAG = "internal_temperature";
#ifdef USE_ESP32
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
static temperature_sensor_handle_t tsensNew = NULL;
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
void InternalTemperatureSensor::update() {
float temperature = NAN;
bool success = false;
#ifdef USE_ESP32
#if defined(USE_ESP32_VARIANT_ESP32)
uint8_t raw = temprature_sens_read();
ESP_LOGV(TAG, "Raw temperature value: %d", raw);
@@ -54,23 +43,7 @@ void InternalTemperatureSensor::update() {
ESP_LOGE(TAG, "Reading failed (%d)", result);
}
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
#ifdef USE_RP2040
temperature = analogReadTemp();
success = (temperature != 0.0f);
#endif // USE_RP2040
#ifdef USE_BK72XX
uint32_t raw, result;
result = temp_single_get_current_temperature(&raw);
success = (result == 0);
#if defined(USE_LIBRETINY_VARIANT_BK7231N)
temperature = raw * -0.38f + 156.0f;
#elif defined(USE_LIBRETINY_VARIANT_BK7231T)
temperature = raw * 0.04f;
#else // USE_LIBRETINY_VARIANT
temperature = raw * 0.128f;
#endif // USE_LIBRETINY_VARIANT
#endif // USE_BK72XX
if (success && std::isfinite(temperature)) {
this->publish_state(temperature);
} else {
@@ -82,7 +55,6 @@ void InternalTemperatureSensor::update() {
}
void InternalTemperatureSensor::setup() {
#ifdef USE_ESP32
#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \
defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32C61) || defined(USE_ESP32_VARIANT_ESP32H2) || \
defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
@@ -102,10 +74,8 @@ void InternalTemperatureSensor::setup() {
return;
}
#endif // USE_ESP32_VARIANT
#endif // USE_ESP32
}
void InternalTemperatureSensor::dump_config() { LOG_SENSOR("", "Internal Temperature Sensor", this); }
} // namespace esphome::internal_temperature
} // namespace internal_temperature
} // namespace esphome
#endif // USE_ESP32
@@ -0,0 +1,31 @@
#ifdef USE_RP2040
#include "esphome/core/log.h"
#include "internal_temperature.h"
#include "Arduino.h"
namespace esphome::internal_temperature {
static const char *const TAG = "internal_temperature.rp2040";
void InternalTemperatureSensor::update() {
float temperature = NAN;
bool success = false;
temperature = analogReadTemp();
success = (temperature != 0.0f);
if (success && std::isfinite(temperature)) {
this->publish_state(temperature);
} else {
ESP_LOGD(TAG, "Ignoring invalid temperature (success=%d, value=%.1f)", success, temperature);
if (!this->has_state()) {
this->publish_state(NAN);
}
}
}
} // namespace esphome::internal_temperature
#endif // USE_RP2040
@@ -0,0 +1,56 @@
#if defined(USE_ZEPHYR) && defined(USE_NRF52)
#include "esphome/core/log.h"
#include "internal_temperature.h"
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
namespace esphome::internal_temperature {
static const char *const TAG = "internal_temperature.zephyr";
static const struct device *const DIE_TEMPERATURE_SENSOR = DEVICE_DT_GET_ONE(nordic_nrf_temp);
void InternalTemperatureSensor::update() {
struct sensor_value value;
int result = sensor_sample_fetch(DIE_TEMPERATURE_SENSOR);
if (result != 0) {
ESP_LOGE(TAG, "Failed to fetch nRF52 die temperature sample (%d)", result);
if (!this->has_state()) {
this->publish_state(NAN);
}
return;
}
result = sensor_channel_get(DIE_TEMPERATURE_SENSOR, SENSOR_CHAN_DIE_TEMP, &value);
if (result != 0) {
ESP_LOGE(TAG, "Failed to get nRF52 die temperature (%d)", result);
if (!this->has_state()) {
this->publish_state(NAN);
}
return;
}
const float temperature = value.val1 + (value.val2 / 1000000.0f);
if (std::isfinite(temperature)) {
this->publish_state(temperature);
} else {
ESP_LOGD(TAG, "Ignoring invalid nRF52 temperature (value=%.1f)", temperature);
if (!this->has_state()) {
this->publish_state(NAN);
}
}
}
void InternalTemperatureSensor::setup() {
if (!device_is_ready(DIE_TEMPERATURE_SENSOR)) {
ESP_LOGE(TAG, "nRF52 die temperature sensor device %s not ready", DIE_TEMPERATURE_SENSOR->name);
this->mark_failed();
return;
}
}
} // namespace esphome::internal_temperature
#endif // USE_ZEPHYR && USE_NRF52
@@ -1,15 +1,20 @@
import esphome.codegen as cg
from esphome.components import sensor
from esphome.components.zephyr import zephyr_add_prj_conf
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_NRF52,
PLATFORM_RP2040,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
PlatformFramework,
)
from esphome.core import CORE
internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature")
InternalTemperatureSensor = internal_temperature_ns.class_(
@@ -25,10 +30,29 @@ CONFIG_SCHEMA = cv.All(
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(cv.polling_component_schema("60s")),
cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040, PLATFORM_BK72XX]),
cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040, PLATFORM_BK72XX, PLATFORM_NRF52]),
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
if CORE.using_zephyr and CORE.is_nrf52:
zephyr_add_prj_conf("SENSOR", True)
zephyr_add_prj_conf("TEMP_NRF5", True)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"internal_temperature_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"internal_temperature_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"internal_temperature_bk72xx.cpp": {
PlatformFramework.BK72XX_ARDUINO,
},
"internal_temperature_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)
+2 -1
View File
@@ -10,6 +10,7 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include <cinttypes>
#include <cmath>
#include <numbers>
@@ -575,7 +576,7 @@ void LD2450Component::handle_periodic_data_() {
if (this->get_timeout_status_(this->presence_millis_)) {
this->target_binary_sensor_->publish_state(false);
} else {
ESP_LOGV(TAG, "Clear presence waiting timeout: %d", this->timeout_);
ESP_LOGV(TAG, "Clear presence waiting timeout: %" PRIu32, this->timeout_);
}
}
}
+9 -4
View File
@@ -108,16 +108,21 @@ bool LibreTinyPreferences::sync() {
}
written++;
} else {
ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.data.size());
ESP_LOGV(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.data.size());
cached++;
}
}
s_pending_save.clear();
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
if (failed > 0) {
ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key);
ESP_LOGE(TAG, "Writing %d items: %d cached, %d written, %d failed. Last error=%d for key=%" PRIu32,
cached + written + failed, cached, written, failed, last_err, last_key);
} else if (written > 0) {
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
} else {
ESP_LOGV(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
}
return failed == 0;
+30 -13
View File
@@ -48,6 +48,7 @@ from esphome.yaml_util import load_yaml
from . import defines as df, helpers, lv_validation as lvalid, widgets
from .automation import focused_widgets, layers_to_code, lvgl_update, refreshed_widgets
from .defines import CONF_ALIGN_TO_LAMBDA_ID
from .encoders import (
ENCODERS_CONFIG,
encoders_to_code,
@@ -69,8 +70,16 @@ from .schemas import (
)
from .styles import styles_to_code, theme_to_code
from .touchscreens import touchscreen_schema, touchscreens_to_code
from .trigger import add_on_boot_triggers, generate_triggers
from .types import IdleTrigger, PlainTrigger, lv_font_t, lv_group_t, lv_style_t, lvgl_ns
from .trigger import add_on_boot_triggers, generate_align_tos, generate_triggers
from .types import (
IdleTrigger,
PlainTrigger,
lv_font_t,
lv_group_t,
lv_lambda_t,
lv_style_t,
lvgl_ns,
)
from .widgets import (
LvScrActType,
Widget,
@@ -345,6 +354,7 @@ async def to_code(configs):
Widget.widgets_completed = True
async with LvContext():
await generate_triggers()
await generate_align_tos(configs[0])
for config in configs:
lv_component = await cg.get_variable(config[CONF_ID])
await generate_page_triggers(config)
@@ -370,7 +380,10 @@ async def to_code(configs):
# This must be done after all widgets are created
for comp in helpers.lvgl_components_required:
cg.add_define(f"USE_LVGL_{comp.upper()}")
lv_image_formats = df.get_color_formats().copy()
for use in helpers.lv_uses:
df.add_define(f"LV_USE_{use.upper()}")
cg.add_define(f"USE_LVGL_{use.upper()}")
if {
"transform_rotation",
"transform_scale",
@@ -378,22 +391,24 @@ async def to_code(configs):
"transform_scale_y",
} & styles_used:
df.add_define("LV_COLOR_SCREEN_TRANSP", "1")
lv_image_formats.add("ARGB8888")
lv_image_formats.add(
"RGB565"
) # Currently always need RGB565 for the display buffer
for use in helpers.lv_uses:
df.add_define(f"LV_USE_{use.upper()}")
cg.add_define(f"USE_LVGL_{use.upper()}")
# Currently always need RGB565 for the display buffer, and ARGB8888 is used for layer blending
lv_image_formats = {"RGB565", "ARGB8888"}
if {
"drop_shadow_color",
"drop_shadow_offset_x",
"drop_shadow_offset_y",
"drop_shadow_opa",
"drop_shadow_quality",
"drop_shadow_radius",
} & styles_used:
lv_image_formats.add("A8")
for image_id in lv_images_used:
await cg.get_variable(image_id)
metadata = get_image_metadata(image_id.id)
image_type = IMAGE_TYPE[metadata.image_type]
transparent = metadata.transparency != CONF_OPAQUE
if transparent:
# Internal draw layer will use ARGB8888
lv_image_formats.add("ARGB8888")
if image_type == ImageBinary:
lv_image_formats.add("I1")
if image_type == ImageGrayscale:
@@ -406,6 +421,7 @@ async def to_code(configs):
lv_image_formats.add("RGB888")
for fmt in lv_image_formats:
df.add_define(f"LV_DRAW_SW_SUPPORT_{fmt}", "1")
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
cg.add_build_flag("-DLV_CONF_H=1")
@@ -458,6 +474,7 @@ LVGL_SCHEMA = cv.All(
.extend(
{
cv.GenerateID(CONF_ID): cv.declare_id(LvglComponent),
cv.GenerateID(CONF_ALIGN_TO_LAMBDA_ID): cv.declare_id(lv_lambda_t),
cv.GenerateID(df.CONF_DISPLAYS): display_schema,
cv.Optional(CONF_COLOR_DEPTH, default=16): cv.one_of(16),
cv.Optional(
+2 -2
View File
@@ -136,7 +136,7 @@ async def update_to_code(config, action_id, template_arg, args):
widget.type.w_type.value_property is not None
and widget.type.w_type.value_property in config
):
lv.event_send(widget.obj, UPDATE_EVENT, nullptr)
lv_obj.send_event(widget.obj, UPDATE_EVENT, nullptr)
widgets = await get_widgets(config[CONF_ID])
return await action_to_code(
@@ -455,6 +455,6 @@ async def obj_refresh_to_code(config, action_id, template_arg, args):
widget.type.w_type.value_property is not None
and widget.type.w_type.value_property in config
):
lv.event_send(widget.obj, UPDATE_EVENT, nullptr)
lv_obj.send_event(widget.obj, UPDATE_EVENT, nullptr)
return await action_to_code(widget, do_refresh, action_id, template_arg, args)
+66 -15
View File
@@ -52,10 +52,6 @@ def get_remapped_uses():
return get_data(KEY_REMAPPED_USES, set())
def get_color_formats():
return get_data(KEY_COLOR_FORMATS, set())
def add_warning(msg: str):
get_warnings().add(msg)
@@ -255,22 +251,75 @@ LV_FONTS = list(f"montserrat_{s}" for s in range(8, 50, 2)) + [
]
LV_EVENT_MAP = {
"PRESS": "PRESSED",
"SHORT_CLICK": "SHORT_CLICKED",
"ALL_EVENTS": "ALL",
"CANCEL": "CANCEL",
"CHANGE": "VALUE_CHANGED",
"CHILD_CHANGE": "CHILD_CHANGED",
"CHILD_CREATE": "CHILD_CREATED",
"CHILD_DELETE": "CHILD_DELETED",
"CLICK": "CLICKED",
"COLOR_FORMAT_CHANGE": "COLOR_FORMAT_CHANGED",
"COVER_CHECK": "COVER_CHECK",
"CREATE": "CREATE",
"DEFOCUS": "DEFOCUSED",
"DELETE": "DELETE",
"DOUBLE_CLICK": "DOUBLE_CLICKED",
"DRAW_MAIN": "DRAW_MAIN",
"DRAW_MAIN_BEGIN": "DRAW_MAIN_BEGIN",
"DRAW_MAIN_END": "DRAW_MAIN_END",
"DRAW_POST": "DRAW_POST",
"DRAW_POST_BEGIN": "DRAW_POST_BEGIN",
"DRAW_POST_END": "DRAW_POST_END",
"DRAW_TASK_ADD": "DRAW_TASK_ADDED",
"FLUSH_FINISH": "FLUSH_FINISH",
"FLUSH_START": "FLUSH_START",
"FLUSH_WAIT_FINISH": "FLUSH_WAIT_FINISH",
"FLUSH_WAIT_START": "FLUSH_WAIT_START",
"FOCUS": "FOCUSED",
"GESTURE": "GESTURE",
"GET_SELF_SIZE": "GET_SELF_SIZE",
"HIT_TEST": "HIT_TEST",
"HOVER_LEAVE": "HOVER_LEAVE",
"HOVER_OVER": "HOVER_OVER",
"INDEV_RESET": "INDEV_RESET",
"INSERT": "INSERT",
"INVALIDATE_AREA": "INVALIDATE_AREA",
"KEY": "KEY",
"LAYOUT_CHANGE": "LAYOUT_CHANGED",
"LEAVE": "LEAVE",
"LONG_PRESS": "LONG_PRESSED",
"LONG_PRESS_REPEAT": "LONG_PRESSED_REPEAT",
"CLICK": "CLICKED",
"PRESS": "PRESSED",
"PRESS_LOST": "PRESS_LOST",
"PRESSING": "PRESSING",
"READY": "READY",
"REFRESH": "REFRESH",
"REFR_EXT_DRAW_SIZE": "REFR_EXT_DRAW_SIZE",
"REFR_READY": "REFR_READY",
"REFR_REQUEST": "REFR_REQUEST",
"REFR_START": "REFR_START",
"RELEASE": "RELEASED",
"RENDER_READY": "RENDER_READY",
"RENDER_START": "RENDER_START",
"RESOLUTION_CHANGE": "RESOLUTION_CHANGED",
"ROTARY": "ROTARY",
"SCREEN_LOAD": "SCREEN_LOADED",
"SCREEN_LOAD_START": "SCREEN_LOAD_START",
"SCREEN_UNLOAD": "SCREEN_UNLOADED",
"SCREEN_UNLOAD_START": "SCREEN_UNLOAD_START",
"SCROLL": "SCROLL",
"SCROLL_BEGIN": "SCROLL_BEGIN",
"SCROLL_END": "SCROLL_END",
"SCROLL": "SCROLL",
"FOCUS": "FOCUSED",
"DEFOCUS": "DEFOCUSED",
"READY": "READY",
"CANCEL": "CANCEL",
"ALL_EVENTS": "ALL",
"CHANGE": "VALUE_CHANGED",
"GESTURE": "GESTURE",
"SCROLL_THROW_BEGIN": "SCROLL_THROW_BEGIN",
"SHORT_CLICK": "SHORT_CLICKED",
"SINGLE_CLICK": "SINGLE_CLICKED",
"SIZE_CHANGE": "SIZE_CHANGED",
"STATE_CHANGE": "STATE_CHANGED",
"STYLE_CHANGE": "STYLE_CHANGED",
"TRIPLE_CLICK": "TRIPLE_CLICKED",
"UPDATE_LAYOUT_COMPLETE": "UPDATE_LAYOUT_COMPLETED",
"VSYNC": "VSYNC",
"VSYNC_REQUEST": "VSYNC_REQUEST",
}
LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP)
@@ -504,6 +553,7 @@ CONF_ACCEPTED_CHARS = "accepted_chars"
CONF_ADJUSTABLE = "adjustable"
CONF_ALIGN = "align"
CONF_ALIGN_TO = "align_to"
CONF_ALIGN_TO_LAMBDA_ID = "align_to_lambda_id"
CONF_ANGLE_RANGE = "angle_range"
CONF_ANIMATED = "animated"
CONF_ANIMATION = "animation"
@@ -540,6 +590,7 @@ CONF_END_ANGLE = "end_angle"
CONF_END_VALUE = "end_value"
CONF_ENTER_BUTTON = "enter_button"
CONF_ENTRIES = "entries"
CONF_EXT_CLICK_AREA = "ext_click_area"
CONF_FLAGS = "flags"
CONF_FLEX_FLOW = "flex_flow"
CONF_FLEX_ALIGN_MAIN = "flex_align_main"
-5
View File
@@ -253,14 +253,10 @@ class MockLv:
A mock object that can be used to generate LVGL calls.
"""
# Mapping for LVGL 9
ATTR_MAP = {"event_send": "obj_send_event", "dither": "bg_dither_mode"}
def __init__(self, base):
self.base = base
def __getattr__(self, attr: str) -> "MockLv":
attr = MockLv.ATTR_MAP.get(attr, attr)
return MockLv(f"{self.base}{attr}")
def append(self, expression):
@@ -314,7 +310,6 @@ class ReturnStatement(ExpressionStatement):
class LvExpr(MockLv):
def __getattr__(self, attr: str) -> "MockLv":
attr = MockLv.ATTR_MAP.get(attr, attr)
return LvExpr(f"{self.base}{attr}")
def append(self, expression):
+13 -13
View File
@@ -343,26 +343,26 @@ void IndicatorLine::set_value(int value) {
}
void IndicatorLine::update_length_() {
uint32_t actual_needle_length;
auto radius = lv_obj_get_width(lv_obj_get_parent(this->obj)) / 2;
auto cx = lv_obj_get_width(lv_obj_get_parent(this->obj)) / 2;
auto cy = lv_obj_get_height(lv_obj_get_parent(this->obj)) / 2;
auto radius = clamp_at_most(cx, cy);
auto length = lv_obj_get_style_length(this->obj, LV_PART_MAIN);
auto radial_offset = lv_obj_get_style_radial_offset(this->obj, LV_PART_MAIN);
if (LV_COORD_IS_PCT(radial_offset)) {
radial_offset = radius * LV_COORD_GET_PCT(radial_offset) / 100;
}
if (LV_COORD_IS_PCT(length)) {
actual_needle_length = radius * LV_COORD_GET_PCT(length) / 100;
length = radius * LV_COORD_GET_PCT(length) / 100;
} else if (length < 0) {
actual_needle_length = radius + length;
} else {
actual_needle_length = length;
length += radius;
}
auto x = lv_trigo_cos(this->angle_) / 32768.0f;
auto y = lv_trigo_sin(this->angle_) / 32768.0f;
// radius here also represents the offset of the scale center from top left
this->points_[0].x = radius + radial_offset * x;
this->points_[0].y = radius + radial_offset * y;
this->points_[1].x = x * actual_needle_length + radius;
this->points_[1].y = y * actual_needle_length + radius;
this->points_[1].x = radius + x * (radial_offset + length);
this->points_[1].y = radius + y * (radial_offset + length);
lv_obj_refresh_self_size(this->obj);
lv_obj_invalidate(this->obj);
}
@@ -682,15 +682,15 @@ void lv_scale_draw_event_cb(lv_event_t *e, int16_t range_start, int16_t range_en
auto *line_dsc = static_cast<lv_draw_line_dsc_t *>(lv_draw_task_get_draw_dsc(task));
int tick = line_dsc->base.id2;
if (tick >= range_start && tick <= range_end) {
unsigned range = range_end - range_start;
int ratio;
if (local) {
int range = range_end - range_start;
tick -= range_start;
ratio = range == 0 ? 0 : (tick * 255) / range;
} else {
range = lv_scale_get_total_tick_count(scale) - 1;
// total tick count is guaranteed to be at least 2.
ratio = (line_dsc->base.id1 * 255) / (lv_scale_get_total_tick_count(scale) - 1);
}
if (range == 0)
range = 1;
auto ratio = (tick * 255) / range;
line_dsc->color = lv_color_mix(color_end, color_start, ratio);
line_dsc->width += width;
}
+17 -6
View File
@@ -74,11 +74,13 @@ inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
#if defined(USE_LVGL_IMAGE) && defined(USE_IMAGE)
// Shortcut / overload, so that the source of an image can easily be updated
// from within a lambda.
inline void lv_image_set_src(lv_obj_t *obj, esphome::image::Image *image) {
lv_image_set_src(obj, image->get_lv_image_dsc());
inline void lv_image_set_src(lv_obj_t *obj, image::Image *image) { lv_image_set_src(obj, image->get_lv_image_dsc()); }
inline void lv_obj_set_style_bitmap_mask_src(lv_obj_t *obj, image::Image *image, lv_style_selector_t selector) {
lv_obj_set_style_bitmap_mask_src(obj, image->get_lv_image_dsc(), selector);
}
inline void lv_obj_set_style_bg_image_src(lv_obj_t *obj, esphome::image::Image *image, lv_style_selector_t selector) {
inline void lv_obj_set_style_bg_image_src(lv_obj_t *obj, image::Image *image, lv_style_selector_t selector) {
lv_obj_set_style_bg_image_src(obj, image->get_lv_image_dsc(), selector);
}
#endif // USE_LVGL_IMAGE
@@ -128,10 +130,19 @@ class LvPageType : public Parented<LvglComponent> {
bool skip;
};
using LvLambdaType = std::function<void(lv_obj_t *)>;
using set_value_lambda_t = std::function<void(float)>;
using event_callback_t = void(lv_event_t *);
using text_lambda_t = std::function<const char *()>;
class LvLambdaComponent : public Component {
public:
LvLambdaComponent(void (*callback)()) : callback_(callback) {}
void setup() override { this->callback_(); }
// execute after the LvglComponent is setup
float get_setup_priority() const override { return setup_priority::PROCESSOR - 5; }
protected:
void (*callback_)();
};
template<typename... Ts> class ObjUpdateAction : public Action<Ts...> {
public:
+2 -2
View File
@@ -12,7 +12,7 @@ from ..lvcode import (
UPDATE_EVENT,
LambdaContext,
ReturnStatement,
lv,
lv_obj,
lvgl_static,
)
from ..types import LV_EVENT, LvNumber, lvgl_ns
@@ -40,7 +40,7 @@ async def to_code(config):
await widget.set_property(
"value", MockObj("v") * MockObj(widget.get_scale()), config[CONF_ANIMATED]
)
lv.event_send(widget.obj, API_EVENT, cg.nullptr)
lv_obj.send_event(widget.obj, API_EVENT, cg.nullptr)
event_code = (
LV_EVENT.VALUE_CHANGED
if not config[CONF_UPDATE_ON_RELEASE]
+54 -14
View File
@@ -146,26 +146,42 @@ def point_schema(value):
# All LVGL styles and their validators
STYLE_PROPS = {
BASE_PROPS = {
"align": df.CHILD_ALIGNMENTS.one_of,
"arc_opa": lvalid.opacity,
"anim_duration": lvalid.lv_milliseconds,
"arc_color": lvalid.lv_color,
"arc_opa": lvalid.opacity,
"arc_rounded": lvalid.lv_bool,
"arc_width": lvalid.pixels,
"anim_time": lvalid.lv_milliseconds,
"base_dir": df.LvConstant("LV_BASE_DIR_", "LTR", "RTL", "AUTO").one_of,
"bg_color": lvalid.lv_color,
"bg_grad": lv_gradient,
"bg_grad_color": lvalid.lv_color,
"bg_dither_mode": df.LvConstant("LV_DITHER_", "NONE", "ORDERED", "ERR_DIFF").one_of,
"bg_grad_dir": LV_GRAD_DIR.one_of,
"bg_grad_opa": lvalid.opacity,
"bg_grad_stop": lvalid.stop_value,
"bg_image_opa": lvalid.opacity,
"bg_image_recolor": lvalid.lv_color,
"bg_image_recolor_opa": lvalid.opacity,
"bg_image_src": lvalid.lv_image,
"bg_image_tiled": lvalid.lv_bool,
"bg_main_opa": lvalid.opacity,
"bg_main_stop": lvalid.stop_value,
"bg_opa": lvalid.opacity,
"bitmap_mask_src": lvalid.lv_image,
"blend_mode": df.LvConstant(
"LV_BLEND_MODE_",
"NORMAL",
"ADDITIVE",
"SUBTRACTIVE",
"MULTIPLY",
"DIFFERENCE",
).one_of,
"blur_backdrop": lvalid.lv_bool,
"blur_quality": df.LvConstant(
"LV_BLUR_QUALITY_", "AUTO", "SPEED", "PRECISION"
).one_of,
"blur_radius": lvalid.lv_positive_int,
"border_color": lvalid.lv_color,
"border_opa": lvalid.opacity,
"border_post": lvalid.lv_bool,
@@ -175,33 +191,53 @@ STYLE_PROPS = {
"border_width": lvalid.lv_positive_int,
"clip_corner": lvalid.lv_bool,
"color_filter_opa": lvalid.opacity,
"drop_shadow_color": lvalid.lv_color,
"drop_shadow_offset_x": lvalid.lv_int,
"drop_shadow_offset_y": lvalid.lv_int,
"drop_shadow_opa": lvalid.opacity,
"drop_shadow_quality": df.LvConstant(
"LV_BLUR_QUALITY_", "AUTO", "SPEED", "PRECISION"
).one_of,
"drop_shadow_radius": lvalid.lv_positive_int,
"height": lvalid.size,
"image_opa": lvalid.opacity,
"image_recolor": lvalid.lv_color,
"image_recolor_opa": lvalid.opacity,
"length": lvalid.pixels_or_percent,
"line_color": lvalid.lv_color,
"line_dash_gap": lvalid.lv_positive_int,
"line_dash_width": lvalid.lv_positive_int,
"line_opa": lvalid.opacity,
"line_rounded": lvalid.lv_bool,
"line_width": lvalid.lv_positive_int,
"margin_bottom": lvalid.padding,
"margin_left": lvalid.padding,
"margin_right": lvalid.padding,
"margin_top": lvalid.padding,
"max_height": lvalid.pixels_or_percent,
"max_width": lvalid.pixels_or_percent,
"min_height": lvalid.pixels_or_percent,
"min_width": lvalid.pixels_or_percent,
"opa": lvalid.opacity,
"opa_layered": lvalid.opacity,
"outline_color": lvalid.lv_color,
"outline_opa": lvalid.opacity,
"outline_pad": lvalid.padding,
"outline_width": lvalid.pixels,
"length": lvalid.pixels_or_percent,
"pad_all": lvalid.padding,
"pad_bottom": lvalid.padding,
"pad_left": lvalid.padding,
"pad_radial": lvalid.padding,
"pad_right": lvalid.padding,
"pad_top": lvalid.padding,
"radial_offset": lvalid.size,
"radius": lvalid.lv_fraction,
"recolor": lvalid.lv_color,
"recolor_opa": lvalid.opacity,
"rotary_sensitivity": lvalid.lv_positive_int,
"shadow_color": lvalid.lv_color,
"shadow_offset_x": lvalid.lv_int,
"shadow_offset_y": lvalid.lv_int,
"shadow_ofs_x": lvalid.lv_int,
"shadow_ofs_y": lvalid.lv_int,
"shadow_opa": lvalid.opacity,
"shadow_spread": lvalid.lv_int,
"shadow_width": lvalid.lv_positive_int,
@@ -216,7 +252,9 @@ STYLE_PROPS = {
"text_letter_space": lvalid.lv_positive_int,
"text_line_space": lvalid.lv_positive_int,
"text_opa": lvalid.opacity,
"transform_angle": lvalid.lv_angle,
"text_outline_stroke_color": lvalid.lv_color,
"text_outline_stroke_opa": lvalid.opacity,
"text_outline_stroke_width": lvalid.lv_positive_int,
"transform_height": lvalid.pixels_or_percent,
"transform_pivot_x": lvalid.pixels_or_percent,
"transform_pivot_y": lvalid.pixels_or_percent,
@@ -226,20 +264,17 @@ STYLE_PROPS = {
"transform_scale_y": lvalid.scale,
"transform_skew_x": lvalid.lv_angle,
"transform_skew_y": lvalid.lv_angle,
"transform_zoom": lvalid.scale,
"transform_width": lvalid.pixels_or_percent,
"translate_radial": lvalid.lv_int,
"translate_x": lvalid.pixels_or_percent,
"translate_y": lvalid.pixels_or_percent,
"max_height": lvalid.pixels_or_percent,
"max_width": lvalid.pixels_or_percent,
"min_height": lvalid.pixels_or_percent,
"min_width": lvalid.pixels_or_percent,
"radius": lvalid.lv_fraction,
"width": lvalid.size,
"x": lvalid.pixels_or_percent,
"y": lvalid.pixels_or_percent,
}
STYLE_REMAP = {
"anim_time": "anim_duration",
"transform_angle": "transform_rotation",
"transform_zoom": "transform_scale",
"zoom": "scale",
@@ -249,6 +284,10 @@ STYLE_REMAP = {
"r_mod": "length",
}
STYLE_PROPS = BASE_PROPS | {
p: BASE_PROPS[v] for p, v in STYLE_REMAP.items() if v in BASE_PROPS
}
def remap_property(prop, record=True):
"""
@@ -394,6 +433,7 @@ def obj_schema(widget_type: WidgetType):
return (
part_schema(widget_type.parts)
.extend(ALIGN_TO_SCHEMA)
.extend({cv.Optional(df.CONF_EXT_CLICK_AREA): lvalid.pixels})
.extend(automation_schema(widget_type.w_type))
.extend(
{
+4 -27
View File
@@ -4,26 +4,12 @@ import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import ID
from .defines import (
CONF_STYLE_DEFINITIONS,
CONF_THEME,
CONF_TOP_LAYER,
LValidator,
literal,
)
from .defines import CONF_STYLE_DEFINITIONS, CONF_THEME, LValidator, literal
from .helpers import add_lv_use
from .lvcode import LambdaContext, LocalVariable, lv
from .lvcode import LambdaContext, lv
from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, remap_property
from .types import ObjUpdateAction, lv_obj_t, lv_style_t
from .widgets import (
Widget,
add_widgets,
collect_parts,
set_obj_properties,
theme_widget_map,
wait_for_widgets,
)
from .widgets.obj import obj_spec
from .types import ObjUpdateAction, lv_style_t
from .widgets import collect_parts, theme_widget_map, wait_for_widgets
def has_style_props(config) -> bool:
@@ -112,12 +98,3 @@ async def theme_to_code(config):
for state, props in states.items()
}
theme_widget_map[w_name] = styles
async def add_top_layer(lv_component, config):
top_layer = lv.disp_get_layer_top(lv_component.var.get_disp())
if top_conf := config.get(CONF_TOP_LAYER):
with LocalVariable("top_layer", lv_obj_t, top_layer) as top_layer_obj:
top_w = Widget(top_layer_obj, obj_spec, top_conf)
await set_obj_properties(top_w, top_conf)
await add_widgets(top_w, top_conf)
+2 -2
View File
@@ -13,8 +13,8 @@ from ..lvcode import (
LambdaContext,
LvConditional,
LvContext,
lv,
lv_add,
lv_obj,
lvgl_static,
)
from ..types import LV_EVENT, LV_STATE, lv_pseudo_button_t, lvgl_ns
@@ -39,7 +39,7 @@ async def to_code(config):
widget.add_state(LV_STATE.CHECKED)
cond.else_()
widget.clear_state(LV_STATE.CHECKED)
lv.event_send(widget.obj, API_EVENT, cg.nullptr)
lv_obj.send_event(widget.obj, API_EVENT, cg.nullptr)
control.add(switch_id.publish_state(v))
switch = cg.new_Pvariable(config[CONF_ID], await control.get_lambda())
await cg.register_component(switch, config)
+2 -2
View File
@@ -10,8 +10,8 @@ from ..lvcode import (
UPDATE_EVENT,
LambdaContext,
LvContext,
lv,
lv_add,
lv_obj,
lvgl_static,
)
from ..types import LV_EVENT, LvText, lvgl_ns
@@ -33,7 +33,7 @@ async def to_code(config):
await wait_for_widgets()
async with LambdaContext([(cg.std_string, "text_value")]) as control:
await widget.set_property("text", "text_value.c_str()")
lv.event_send(widget.obj, API_EVENT, cg.nullptr)
lv_obj.send_event(widget.obj, API_EVENT, cg.nullptr)
control.add(textvar.publish_state(widget.get_value()))
async with LambdaContext(EVENT_ARG) as lamb:
lv_add(textvar.publish_state(widget.get_value()))
+26 -2
View File
@@ -8,10 +8,14 @@ from esphome.const import (
CONF_X,
CONF_Y,
)
from esphome.cpp_generator import new_Pvariable
from esphome.cpp_helpers import register_component
from .defines import (
CONF_ALIGN,
CONF_ALIGN_TO,
CONF_ALIGN_TO_LAMBDA_ID,
CONF_EXT_CLICK_AREA,
DIRECTIONS,
LV_EVENT_MAP,
LV_EVENT_TRIGGERS,
@@ -89,13 +93,33 @@ async def generate_triggers():
await add_on_boot_triggers(w.config.get(CONF_ON_BOOT, ()))
# Generate align to directives while we're here
if align_to := w.config.get(CONF_ALIGN_TO):
async def generate_align_tos(config: dict):
"""
Called once, with a full lvgl configuration to emit deferred align_to actions as a component
that executes after the LVGL setup. This is required since align_to actions are not recalculated on layout changes
and so must be applied after the display is properly laid out.
:param config:
:return:
"""
align_tos = tuple(
w for w in widget_map.values() if w.config and CONF_ALIGN_TO in w.config
)
if align_tos:
async with LambdaContext(where="align_to") as context:
for w in align_tos:
align_to = w.config[CONF_ALIGN_TO]
target = widget_map[align_to[CONF_ID]].obj
align = literal(align_to[CONF_ALIGN])
x = align_to[CONF_X]
y = align_to[CONF_Y]
lv.obj_align_to(w.obj, target, align, x, y)
if ext_click_area := w.config.get(CONF_EXT_CLICK_AREA):
lv.obj_set_ext_click_area(w.obj, ext_click_area)
action_id = config[CONF_ALIGN_TO_LAMBDA_ID]
var = new_Pvariable(action_id, await context.get_lambda())
await register_component(var, {})
async def add_trigger(conf, w, *events, is_selected=None):
+2 -2
View File
@@ -1,7 +1,7 @@
from esphome import automation, codegen as cg
from esphome.const import CONF_TEXT, CONF_VALUE
from esphome.cpp_generator import MockObj
from esphome.cpp_types import esphome_ns
from esphome.cpp_types import Component, esphome_ns
from .defines import lvgl_ns
@@ -51,7 +51,7 @@ IdleTrigger = lvgl_ns.class_("IdleTrigger", automation.Trigger.template())
ObjUpdateAction = lvgl_ns.class_("ObjUpdateAction", automation.Action)
LvglCondition = lvgl_ns.class_("LvglCondition", automation.Condition)
LvglAction = lvgl_ns.class_("LvglAction", automation.Action)
lv_lambda_t = lvgl_ns.class_("LvLambdaType")
lv_lambda_t = lvgl_ns.class_("LvLambdaComponent", Component)
LvCompound = lvgl_ns.class_("LvCompound")
lv_font_t = cg.global_ns.class_("lv_font_t")
lv_style_t = cg.global_ns.struct("lv_style_t")
+5 -2
View File
@@ -1,5 +1,4 @@
import sys
from typing import Any
from esphome import codegen as cg, config_validation as cv
from esphome.automation import register_action
@@ -405,7 +404,11 @@ class Widget:
# Map of widgets to their config, used for trigger generation
widget_map: dict[Any, Widget] = {}
widget_map: dict[ID, Widget] = {}
def is_widget_completed(name: ID) -> bool:
return name in widget_map
class LvScrActType(WidgetType):
@@ -42,7 +42,6 @@ from ..defines import (
CONF_SRC,
CONF_START_ANGLE,
addr,
get_color_formats,
literal,
)
from ..lv_validation import (
@@ -99,7 +98,6 @@ class CanvasType(WidgetType):
# RGB565 is 16-bit (2 bytes per pixel), ARGB8888 is 32-bit (4 bytes per pixel)
if config[CONF_TRANSPARENT]:
color_format = "LV_COLOR_FORMAT_ARGB8888"
get_color_formats().add("ARGB8888")
else:
color_format = "LV_COLOR_FORMAT_NATIVE"
+20 -6
View File
@@ -1,12 +1,15 @@
from esphome.components.key_provider import KeyProvider
import esphome.config_validation as cv
from esphome.const import CONF_ITEMS, CONF_MODE
from esphome.core import CORE
from esphome.cpp_types import std_string
from .. import LvContext
from ..defines import CONF_MAIN, KEYBOARD_MODES, literal
from ..helpers import add_lv_use, lvgl_components_required
from ..helpers import lvgl_components_required
from ..types import LvCompound, LvType
from . import Widget, WidgetType, get_widgets
from . import Widget, WidgetType, get_widgets, is_widget_completed
from .buttonmatrix import CONF_BUTTONMATRIX
from .textarea import CONF_TEXTAREA, lv_textarea_t
CONF_KEYBOARD = "keyboard"
@@ -41,16 +44,27 @@ class KeyboardType(WidgetType):
)
def get_uses(self):
return CONF_KEYBOARD, CONF_TEXTAREA
return CONF_KEYBOARD, CONF_TEXTAREA, CONF_BUTTONMATRIX
async def to_code(self, w: Widget, config: dict):
lvgl_components_required.add("KEY_LISTENER")
lvgl_components_required.add(CONF_KEYBOARD)
add_lv_use("btnmatrix")
if mode := config.get(CONF_MODE):
await w.set_property(CONF_MODE, await KEYBOARD_MODES.process(mode))
if ta := await get_widgets(config, CONF_TEXTAREA):
await w.set_property(CONF_TEXTAREA, ta[0].obj)
if textarea := config.get(CONF_TEXTAREA):
# If a textarea is configured, it must be generated before the keyboard can attach it.
# If not yet configured, defer the attachment code.
async def add_textarea():
async with LvContext():
await w.set_property(
CONF_TEXTAREA, (await get_widgets(config, CONF_TEXTAREA))[0].obj
)
if is_widget_completed(textarea):
await add_textarea()
else:
CORE.add_job(add_textarea)
keyboard_spec = KeyboardType()
+1 -1
View File
@@ -35,7 +35,7 @@ class LabelType(WidgetType):
if (value := config.get(CONF_TEXT)) is not None:
await w.set_property(CONF_TEXT, await lv_text.process(value))
await w.set_property(CONF_LONG_MODE, config)
await w.set_property(CONF_RECOLOR, config)
await w.set_property(CONF_RECOLOR, config, processor=lv_bool)
label_spec = LabelType()
+8 -11
View File
@@ -17,11 +17,6 @@ lv_point_t = cg.global_ns.struct("lv_point_t")
lv_point_precise_t = cg.global_ns.struct("lv_point_precise_t")
LINE_SCHEMA = {
cv.Required(CONF_POINTS): cv.ensure_list(point_schema),
}
async def process_coord(coord):
if isinstance(coord, Lambda):
return call_lambda(await cg.process_lambda(coord, [], return_type=lv_coord_t))
@@ -34,15 +29,17 @@ class LineType(WidgetType):
CONF_LINE,
LvType("LvLineType", parents=(LvCompound,)),
(CONF_MAIN,),
LINE_SCHEMA,
schema={cv.Required(CONF_POINTS): cv.ensure_list(point_schema)},
modify_schema={cv.Optional(CONF_POINTS): cv.ensure_list(point_schema)},
)
async def to_code(self, w: Widget, config):
points = [
[await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
for p in config[CONF_POINTS]
]
lv_add(w.var.set_points(points))
if CONF_POINTS in config:
points = [
[await process_coord(p[CONF_X]), await process_coord(p[CONF_Y])]
for p in config[CONF_POINTS]
]
lv_add(w.var.set_points(points))
line_spec = LineType()
+35 -28
View File
@@ -56,11 +56,11 @@ from ..lv_validation import (
lv_float,
lv_image,
lv_int,
lv_positive_int,
opacity,
padding,
pixels,
pixels_or_percent,
pixels_or_percent_validator,
requires_component,
size,
)
@@ -88,7 +88,10 @@ CONF_COLOR_START = "color_start"
CONF_DRAW_TICKS_ON_TOP = "draw_ticks_on_top"
CONF_IMAGE_ID = "image_id"
CONF_INDICATORS = "indicators"
CONF_DASH_GAP = "dash_gap"
CONF_DASH_WIDTH = "dash_width"
CONF_LINE_ID = "line_id"
CONF_ROUNDED = "rounded"
CONF_LABEL_GAP = "label_gap"
CONF_MAJOR = "major"
CONF_METER = "meter"
@@ -135,9 +138,12 @@ INDICATOR_LINE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_WIDTH, default=4): cv.int_,
cv.Optional(CONF_COLOR, default=0): lv_color,
cv.Optional(CONF_ROUNDED, default=True): lv_bool,
cv.Optional(CONF_DASH_GAP): lv_positive_int,
cv.Optional(CONF_DASH_WIDTH): lv_positive_int,
cv.Optional(CONF_R_MOD): padding,
cv.Optional(CONF_LENGTH): pixels_or_percent_validator,
cv.Optional(CONF_RADIAL_OFFSET, 0): pixels_or_percent_validator,
cv.Optional(CONF_LENGTH): pixels_or_percent,
cv.Optional(CONF_RADIAL_OFFSET): pixels_or_percent,
cv.Optional(CONF_VALUE, default=0.0): lv_float,
cv.Optional(CONF_OPA, default=1.0): opacity,
}
@@ -249,17 +255,17 @@ SCALE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_COUNT, default=12): cv.int_range(min=2),
cv.Optional(CONF_WIDTH, default=2): cv.positive_int,
cv.Optional(CONF_LENGTH, default=10): size,
cv.Optional(CONF_RADIAL_OFFSET, default=0): size,
cv.Optional(CONF_LENGTH, default=10): cv.positive_int,
cv.Optional(CONF_RADIAL_OFFSET): cv.positive_int,
cv.Optional(CONF_COLOR, default=0x808080): lv_color,
cv.Optional(CONF_MAJOR): cv.Schema(
{
cv.Optional(CONF_STRIDE, default=3): cv.positive_int,
cv.Optional(CONF_WIDTH, default=5): size,
cv.Optional(CONF_LENGTH, default="15%"): size,
cv.Optional(CONF_RADIAL_OFFSET, default=0): size,
cv.Optional(CONF_LENGTH, default=12): cv.positive_int,
cv.Optional(CONF_RADIAL_OFFSET): cv.positive_int,
cv.Optional(CONF_COLOR, default=0): lv_color,
cv.Optional(CONF_LABEL_GAP, default=4): size,
cv.Optional(CONF_LABEL_GAP, default=4): cv.int_,
}
),
}
@@ -466,11 +472,15 @@ class MeterType(WidgetType):
CONF_OPA: v[CONF_OPA],
CONF_LINE_WIDTH: v[CONF_WIDTH],
"line_color": v[CONF_COLOR],
"line_rounded": True,
"line_rounded": v[CONF_ROUNDED],
CONF_ALIGN: CHILD_ALIGNMENTS.TOP_LEFT,
CONF_LENGTH: length,
CONF_RADIAL_OFFSET: v[CONF_RADIAL_OFFSET],
}
if radial_offset := v.get(CONF_RADIAL_OFFSET):
props[CONF_RADIAL_OFFSET] = radial_offset
for option in (CONF_DASH_WIDTH, CONF_DASH_GAP):
if option in v:
props["line_" + option] = v[option]
lw = await widget_to_code(props, line_indicator_type, scale_var)
await set_indicator_values(lw, v)
@@ -478,10 +488,8 @@ class MeterType(WidgetType):
add_lv_use(CONF_IMAGE)
src = v[CONF_SRC]
src_data = get_image_metadata(src.id)
pivot_x = await pixels.process(v[CONF_PIVOT_X])
pivot_y = await pixels.process(
v.get(CONF_PIVOT_Y, src_data.height // 2)
)
pivot_x = v[CONF_PIVOT_X]
pivot_y = v.get(CONF_PIVOT_Y, src_data.height // 2)
props = {
CONF_X: src_data.width // 2 - pivot_x,
"transform_pivot_x": pivot_x,
@@ -511,11 +519,12 @@ class MeterType(WidgetType):
lv_obj.set_style_line_width(
scale_var, await size.process(ticks[CONF_WIDTH]), LV_PART.ITEMS
)
lv_obj.set_style_radial_offset(
scale_var,
await size.process(ticks[CONF_RADIAL_OFFSET]),
LV_PART.ITEMS,
)
if radial_offset := ticks.get(CONF_RADIAL_OFFSET):
lv_obj.set_style_radial_offset(
scale_var,
-radial_offset,
LV_PART.ITEMS,
)
lv_obj.set_style_line_color(
scale_var,
await lv_color.process(ticks[CONF_COLOR]),
@@ -536,11 +545,12 @@ class MeterType(WidgetType):
await size.process(major[CONF_LENGTH]),
LV_PART.INDICATOR,
)
lv_obj.set_style_radial_offset(
scale_var,
await size.process(ticks[CONF_RADIAL_OFFSET]),
LV_PART.INDICATOR,
)
if radial_offset := major.get(CONF_RADIAL_OFFSET):
lv_obj.set_style_radial_offset(
scale_var,
-radial_offset,
LV_PART.INDICATOR,
)
lv_obj.set_style_line_width(
scale_var,
await size.process(major[CONF_WIDTH]),
@@ -553,12 +563,9 @@ class MeterType(WidgetType):
)
# Set label gap (padding)
label_gap = await size.process(major[CONF_LABEL_GAP])
if isinstance(label_gap, int):
label_gap -= DEFAULT_LABEL_GAP
lv_obj.set_style_pad_radial(
scale_var,
label_gap,
major[CONF_LABEL_GAP] - DEFAULT_LABEL_GAP,
LV_PART.INDICATOR,
)
else:
+5 -3
View File
@@ -33,6 +33,7 @@ from ..styles import LVStyle
from ..types import LV_EVENT, lv_obj_t
from . import Widget, WidgetType, add_widgets, set_obj_properties, widget_to_code
from .button import button_spec, lv_button_t
from .img import CONF_IMAGE
from .label import CONF_LABEL
from .obj import obj_spec
@@ -41,7 +42,7 @@ CONF_MSGBOX = "msgbox"
OUTER_STYLE = LVStyle(
"msgbox_outer",
{
"bg_opa": 128,
"bg_opa": 0.5,
"bg_color": "black",
"border_width": 0,
"pad_all": 0,
@@ -119,6 +120,7 @@ async def msgbox_to_code(top_layer, conf):
CONF_BUTTON,
CONF_LABEL,
CONF_MSGBOX,
CONF_IMAGE,
*button_spec.get_uses(),
)
if CONF_BUTTON_STYLE in conf:
@@ -156,7 +158,7 @@ async def msgbox_to_code(top_layer, conf):
with LocalVariable(
"close_btn_", lv_obj_t, lv_expr.msgbox_add_close_button(msgbox)
) as close_btn:
lv_obj.remove_event_cb(close_btn, nullptr)
lv_obj.remove_event(close_btn, 0)
lv_obj.add_event_cb(
close_btn,
await close_action.get_lambda(),
@@ -170,6 +172,6 @@ async def msgbox_to_code(top_layer, conf):
async def msgboxes_to_code(lv_component, config):
top_layer = lv.disp_get_layer_top(lv_component.get_disp())
top_layer = lv_expr.disp_get_layer_top(lv_component.get_disp())
for conf in config.get(CONF_MSGBOXES, ()):
await msgbox_to_code(top_layer, conf)
+1 -2
View File
@@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_SIZE, CONF_TEXT
from ..defines import CONF_MAIN, get_color_formats
from ..defines import CONF_MAIN
from ..lv_validation import color, lv_color, lv_int, lv_text
from ..lvcode import LocalVariable, lv
from ..schemas import TEXT_SCHEMA
@@ -44,7 +44,6 @@ class QrCodeType(WidgetType):
return CONF_CANVAS, CONF_IMAGE
async def to_code(self, w: Widget, config):
get_color_formats().add("ARGB8888")
await w.set_property(
CONF_LIGHT_COLOR, await lv_color.process(config.get(CONF_LIGHT_COLOR))
)
+2 -2
View File
@@ -26,7 +26,7 @@ from ..schemas import container_schema, part_schema
from ..types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr
from . import Widget, WidgetType, add_widgets, get_widgets, set_obj_properties
from .button import button_spec
from .buttonmatrix import buttonmatrix_spec
from .buttonmatrix import CONF_BUTTONMATRIX, buttonmatrix_spec
from .obj import obj_spec
CONF_TABVIEW = "tabview"
@@ -73,7 +73,7 @@ class TabviewType(WidgetType):
)
def get_uses(self):
return "btnmatrix", TYPE_FLEX
return CONF_BUTTONMATRIX, TYPE_FLEX
async def to_code(self, w: Widget, config: dict):
await w.set_property(
+1 -1
View File
@@ -129,6 +129,6 @@ async def tileview_select(config, action_id, template_arg, args):
lv.tileview_set_tile_by_index(
widgets[0].obj, column, row, literal(config[CONF_ANIMATED])
)
lv.event_send(w.obj, LV_EVENT.VALUE_CHANGED, cg.nullptr)
lv_obj.send_event(w.obj, LV_EVENT.VALUE_CHANGED, cg.nullptr)
return await action_to_code(widgets, do_select, action_id, template_arg, args)
@@ -6,6 +6,7 @@
#include "max7219font.h"
#include <algorithm>
#include <cinttypes>
namespace esphome {
namespace max7219digit {
@@ -92,7 +93,9 @@ void MAX7219Component::loop() {
if (this->scroll_mode_ == ScrollMode::STOP) {
if (static_cast<size_t>(this->stepsleft_ + get_width_internal()) == first_line_size + 1) {
if (millis_since_last_scroll < this->scroll_dwell_) {
ESP_LOGVV(TAG, "Dwell time at end of string in case of stop at end. Step %d, since last scroll %d, dwell %d.",
ESP_LOGVV(TAG,
"Dwell time at end of string in case of stop at end. Step %d, since last scroll %" PRIu32
", dwell %d.",
this->stepsleft_, millis_since_last_scroll, this->scroll_dwell_);
return;
}
@@ -0,0 +1,41 @@
import esphome.codegen as cg
from esphome.components import climate, uart
import esphome.config_validation as cv
from esphome.const import CONF_UPDATE_INTERVAL
from esphome.types import ConfigType
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["climate"]
CODEOWNERS = ["@crnjan"]
mitsubishi_ns = cg.esphome_ns.namespace("mitsubishi_cn105")
MitsubishiCN105Climate = mitsubishi_ns.class_(
"MitsubishiCN105Climate",
climate.Climate,
cg.Component,
uart.UARTDevice,
)
CONFIG_SCHEMA = (
climate.climate_schema(MitsubishiCN105Climate)
.extend(uart.UART_DEVICE_SCHEMA)
.extend({cv.Optional(CONF_UPDATE_INTERVAL, default="1s"): cv.update_interval})
)
FINAL_VALIDATE_SCHEMA = cv.All(
uart.final_validate_device_schema(
"mitsubishi_cn105",
require_rx=True,
require_tx=True,
data_bits=8,
parity="EVEN",
stop_bits=1,
)
)
async def to_code(config: ConfigType) -> None:
var = await climate.new_climate(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
@@ -0,0 +1,7 @@
#include "mitsubishi_cn105.h"
namespace esphome::mitsubishi_cn105 {
static const char *const TAG = "mitsubishi_cn105.driver";
} // namespace esphome::mitsubishi_cn105
@@ -0,0 +1,19 @@
#pragma once
#include "esphome/components/uart/uart.h"
namespace esphome::mitsubishi_cn105 {
class MitsubishiCN105 {
public:
explicit MitsubishiCN105(uart::UARTDevice &device) : device_(device) {}
uint32_t get_update_interval() const { return this->update_interval_ms_; }
void set_update_interval(uint32_t interval_ms) { this->update_interval_ms_ = interval_ms; }
protected:
uart::UARTDevice &device_;
uint32_t update_interval_ms_{1000};
};
} // namespace esphome::mitsubishi_cn105
@@ -0,0 +1,28 @@
#include "mitsubishi_cn105_climate.h"
#include "esphome/core/log.h"
namespace esphome::mitsubishi_cn105 {
static const char *const TAG = "mitsubishi_cn105.climate";
void MitsubishiCN105Climate::dump_config() {
LOG_CLIMATE("", "Mitsubishi CN105 Climate", this);
ESP_LOGCONFIG(TAG,
" Update interval: %" PRIu32 " ms\n"
" UART: baud_rate=%" PRIu32 " data_bits=%u parity=%s stop_bits=%u",
this->hp_.get_update_interval(), this->parent_->get_baud_rate(), this->parent_->get_data_bits(),
LOG_STR_ARG(parity_to_str(this->parent_->get_parity())), this->parent_->get_stop_bits());
}
void MitsubishiCN105Climate::setup() {}
void MitsubishiCN105Climate::loop() {}
climate::ClimateTraits MitsubishiCN105Climate::traits() {
climate::ClimateTraits traits;
return traits;
}
void MitsubishiCN105Climate::control(const climate::ClimateCall &call) {}
} // namespace esphome::mitsubishi_cn105
@@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/climate/climate.h"
#include "esphome/components/uart/uart.h"
#include "mitsubishi_cn105.h"
namespace esphome::mitsubishi_cn105 {
class MitsubishiCN105Climate : public climate::Climate, public Component, public uart::UARTDevice {
public:
explicit MitsubishiCN105Climate() : hp_(*this) {}
void setup() override;
void loop() override;
void dump_config() override;
climate::ClimateTraits traits() override;
void control(const climate::ClimateCall &call) override;
void set_update_interval(uint32_t ms) { hp_.set_update_interval(ms); }
protected:
MitsubishiCN105 hp_;
};
} // namespace esphome::mitsubishi_cn105
+137 -137
View File
@@ -597,173 +597,173 @@ void MixerSpeaker::audio_mixer_task(void *params) {
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_STARTING);
std::unique_ptr<audio::AudioSinkTransferBuffer> output_transfer_buffer = audio::AudioSinkTransferBuffer::create(
this_mixer->audio_stream_info_.value().ms_to_bytes(TRANSFER_BUFFER_DURATION_MS));
{ // Ensure C++ objects fall out of scope to ensure proper cleanup before stopping the task
std::unique_ptr<audio::AudioSinkTransferBuffer> output_transfer_buffer = audio::AudioSinkTransferBuffer::create(
this_mixer->audio_stream_info_.value().ms_to_bytes(TRANSFER_BUFFER_DURATION_MS));
if (output_transfer_buffer == nullptr) {
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_STOPPED | MIXER_TASK_ERR_ESP_NO_MEM);
if (output_transfer_buffer == nullptr) {
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_STOPPED | MIXER_TASK_ERR_ESP_NO_MEM);
vTaskSuspend(nullptr); // Suspend this task indefinitely until the loop method deletes it
}
output_transfer_buffer->set_sink(this_mixer->output_speaker_);
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_RUNNING);
bool sent_finished = false;
// Pre-allocate vectors to avoid heap allocation in the loop (max 8 source speakers per schema)
FixedVector<SourceSpeaker *> speakers_with_data;
FixedVector<std::shared_ptr<audio::AudioSourceTransferBuffer>> transfer_buffers_with_data;
speakers_with_data.init(this_mixer->source_speakers_.size());
transfer_buffers_with_data.init(this_mixer->source_speakers_.size());
while (true) {
uint32_t event_group_bits = xEventGroupGetBits(this_mixer->event_group_);
if (event_group_bits & MIXER_TASK_COMMAND_STOP) {
break;
vTaskSuspend(nullptr); // Suspend this task indefinitely until the loop method deletes it
}
// Never shift the data in the output transfer buffer to avoid unnecessary, slow data moves
output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS), false);
output_transfer_buffer->set_sink(this_mixer->output_speaker_);
const uint32_t output_frames_free =
this_mixer->audio_stream_info_.value().bytes_to_frames(output_transfer_buffer->free());
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_RUNNING);
speakers_with_data.clear();
transfer_buffers_with_data.clear();
bool sent_finished = false;
for (auto &speaker : this_mixer->source_speakers_) {
if (speaker->is_running() && !speaker->get_pause_state()) {
// Speaker is running and not paused, so it possibly can provide audio data
std::shared_ptr<audio::AudioSourceTransferBuffer> transfer_buffer = speaker->get_transfer_buffer().lock();
if (transfer_buffer.use_count() == 0) {
// No transfer buffer allocated, so skip processing this speaker
continue;
}
speaker->process_data_from_source(transfer_buffer, 0); // Transfers and ducks audio from source ring buffers
// Pre-allocate vectors to avoid heap allocation in the loop (max 8 source speakers per schema)
FixedVector<SourceSpeaker *> speakers_with_data;
FixedVector<std::shared_ptr<audio::AudioSourceTransferBuffer>> transfer_buffers_with_data;
speakers_with_data.init(this_mixer->source_speakers_.size());
transfer_buffers_with_data.init(this_mixer->source_speakers_.size());
if (transfer_buffer->available() > 0) {
// Store the locked transfer buffers in their own vector to avoid releasing ownership until after the loop
transfer_buffers_with_data.push_back(transfer_buffer);
speakers_with_data.push_back(speaker);
while (true) {
uint32_t event_group_bits = xEventGroupGetBits(this_mixer->event_group_);
if (event_group_bits & MIXER_TASK_COMMAND_STOP) {
break;
}
// Never shift the data in the output transfer buffer to avoid unnecessary, slow data moves
output_transfer_buffer->transfer_data_to_sink(pdMS_TO_TICKS(TASK_DELAY_MS), false);
const uint32_t output_frames_free =
this_mixer->audio_stream_info_.value().bytes_to_frames(output_transfer_buffer->free());
speakers_with_data.clear();
transfer_buffers_with_data.clear();
for (auto &speaker : this_mixer->source_speakers_) {
if (speaker->is_running() && !speaker->get_pause_state()) {
// Speaker is running and not paused, so it possibly can provide audio data
std::shared_ptr<audio::AudioSourceTransferBuffer> transfer_buffer = speaker->get_transfer_buffer().lock();
if (transfer_buffer.use_count() == 0) {
// No transfer buffer allocated, so skip processing this speaker
continue;
}
speaker->process_data_from_source(transfer_buffer, 0); // Transfers and ducks audio from source ring buffers
if (transfer_buffer->available() > 0) {
// Store the locked transfer buffers in their own vector to avoid releasing ownership until after the loop
transfer_buffers_with_data.push_back(transfer_buffer);
speakers_with_data.push_back(speaker);
}
}
}
}
if (transfer_buffers_with_data.empty()) {
// No audio available for transferring, block task temporarily
delay(TASK_DELAY_MS);
continue;
}
if (transfer_buffers_with_data.empty()) {
// No audio available for transferring, block task temporarily
delay(TASK_DELAY_MS);
continue;
}
uint32_t frames_to_mix = output_frames_free;
uint32_t frames_to_mix = output_frames_free;
if ((transfer_buffers_with_data.size() == 1) || this_mixer->queue_mode_) {
// Only one speaker has audio data, just copy samples over
if ((transfer_buffers_with_data.size() == 1) || this_mixer->queue_mode_) {
// Only one speaker has audio data, just copy samples over
audio::AudioStreamInfo active_stream_info = speakers_with_data[0]->get_audio_stream_info();
audio::AudioStreamInfo active_stream_info = speakers_with_data[0]->get_audio_stream_info();
if (active_stream_info.get_sample_rate() ==
this_mixer->output_speaker_->get_audio_stream_info().get_sample_rate()) {
// Speaker's sample rate matches the output speaker's, copy directly
if (active_stream_info.get_sample_rate() ==
this_mixer->output_speaker_->get_audio_stream_info().get_sample_rate()) {
// Speaker's sample rate matches the output speaker's, copy directly
const uint32_t frames_available_in_buffer =
active_stream_info.bytes_to_frames(transfer_buffers_with_data[0]->available());
frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
copy_frames(reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start()), active_stream_info,
reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
this_mixer->audio_stream_info_.value(), frames_to_mix);
const uint32_t frames_available_in_buffer =
active_stream_info.bytes_to_frames(transfer_buffers_with_data[0]->available());
frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
copy_frames(reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start()),
active_stream_info, reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
this_mixer->audio_stream_info_.value(), frames_to_mix);
// Set playback delay for newly contributing source
if (!speakers_with_data[0]->has_contributed_.load(std::memory_order_acquire)) {
speakers_with_data[0]->playback_delay_frames_.store(
this_mixer->frames_in_pipeline_.load(std::memory_order_acquire), std::memory_order_release);
speakers_with_data[0]->has_contributed_.store(true, std::memory_order_release);
// Set playback delay for newly contributing source
if (!speakers_with_data[0]->has_contributed_.load(std::memory_order_acquire)) {
speakers_with_data[0]->playback_delay_frames_.store(
this_mixer->frames_in_pipeline_.load(std::memory_order_acquire), std::memory_order_release);
speakers_with_data[0]->has_contributed_.store(true, std::memory_order_release);
}
// Update source speaker pending frames
speakers_with_data[0]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
transfer_buffers_with_data[0]->decrease_buffer_length(active_stream_info.frames_to_bytes(frames_to_mix));
// Update output transfer buffer length and pipeline frame count
output_transfer_buffer->increase_buffer_length(
this_mixer->audio_stream_info_.value().frames_to_bytes(frames_to_mix));
this_mixer->frames_in_pipeline_.fetch_add(frames_to_mix, std::memory_order_release);
} else {
// Speaker's stream info doesn't match the output speaker's, so it's a new source speaker
if (!this_mixer->output_speaker_->is_stopped()) {
if (!sent_finished) {
this_mixer->output_speaker_->finish();
sent_finished = true; // Avoid repeatedly sending the finish command
}
} else {
// Speaker has finished writing the current audio, update the stream information and restart the speaker
this_mixer->audio_stream_info_ =
audio::AudioStreamInfo(active_stream_info.get_bits_per_sample(), this_mixer->output_channels_,
active_stream_info.get_sample_rate());
this_mixer->output_speaker_->set_audio_stream_info(this_mixer->audio_stream_info_.value());
this_mixer->output_speaker_->start();
// Reset pipeline frame count since we're starting fresh with a new sample rate
this_mixer->frames_in_pipeline_.store(0, std::memory_order_release);
sent_finished = false;
}
}
} else {
// Determine how many frames to mix
for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) {
const uint32_t frames_available_in_buffer = speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(
transfer_buffers_with_data[i]->available());
frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
}
int16_t *primary_buffer = reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start());
audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info();
// Mix two streams together
for (size_t i = 1; i < transfer_buffers_with_data.size(); ++i) {
mix_audio_samples(primary_buffer, primary_stream_info,
reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()),
speakers_with_data[i]->get_audio_stream_info(),
reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
this_mixer->audio_stream_info_.value(), frames_to_mix);
if (i != transfer_buffers_with_data.size() - 1) {
// Need to mix more streams together, point primary buffer and stream info to the already mixed output
primary_buffer = reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end());
primary_stream_info = this_mixer->audio_stream_info_.value();
}
}
// Update source speaker pending frames
speakers_with_data[0]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
transfer_buffers_with_data[0]->decrease_buffer_length(active_stream_info.frames_to_bytes(frames_to_mix));
// Get current pipeline depth for delay calculation (before incrementing)
uint32_t current_pipeline_frames = this_mixer->frames_in_pipeline_.load(std::memory_order_acquire);
// Update output transfer buffer length and pipeline frame count
// Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks
for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) {
// Set playback delay for newly contributing sources
if (!speakers_with_data[i]->has_contributed_.load(std::memory_order_acquire)) {
speakers_with_data[i]->playback_delay_frames_.store(current_pipeline_frames, std::memory_order_release);
speakers_with_data[i]->has_contributed_.store(true, std::memory_order_release);
}
speakers_with_data[i]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
transfer_buffers_with_data[i]->decrease_buffer_length(
speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix));
}
// Update output transfer buffer length and pipeline frame count (once, not per source)
output_transfer_buffer->increase_buffer_length(
this_mixer->audio_stream_info_.value().frames_to_bytes(frames_to_mix));
this_mixer->frames_in_pipeline_.fetch_add(frames_to_mix, std::memory_order_release);
} else {
// Speaker's stream info doesn't match the output speaker's, so it's a new source speaker
if (!this_mixer->output_speaker_->is_stopped()) {
if (!sent_finished) {
this_mixer->output_speaker_->finish();
sent_finished = true; // Avoid repeatedly sending the finish command
}
} else {
// Speaker has finished writing the current audio, update the stream information and restart the speaker
this_mixer->audio_stream_info_ =
audio::AudioStreamInfo(active_stream_info.get_bits_per_sample(), this_mixer->output_channels_,
active_stream_info.get_sample_rate());
this_mixer->output_speaker_->set_audio_stream_info(this_mixer->audio_stream_info_.value());
this_mixer->output_speaker_->start();
// Reset pipeline frame count since we're starting fresh with a new sample rate
this_mixer->frames_in_pipeline_.store(0, std::memory_order_release);
sent_finished = false;
}
}
} else {
// Determine how many frames to mix
for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) {
const uint32_t frames_available_in_buffer =
speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available());
frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer);
}
int16_t *primary_buffer = reinterpret_cast<int16_t *>(transfer_buffers_with_data[0]->get_buffer_start());
audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info();
// Mix two streams together
for (size_t i = 1; i < transfer_buffers_with_data.size(); ++i) {
mix_audio_samples(primary_buffer, primary_stream_info,
reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()),
speakers_with_data[i]->get_audio_stream_info(),
reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end()),
this_mixer->audio_stream_info_.value(), frames_to_mix);
if (i != transfer_buffers_with_data.size() - 1) {
// Need to mix more streams together, point primary buffer and stream info to the already mixed output
primary_buffer = reinterpret_cast<int16_t *>(output_transfer_buffer->get_buffer_end());
primary_stream_info = this_mixer->audio_stream_info_.value();
}
}
// Get current pipeline depth for delay calculation (before incrementing)
uint32_t current_pipeline_frames = this_mixer->frames_in_pipeline_.load(std::memory_order_acquire);
// Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks
for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) {
// Set playback delay for newly contributing sources
if (!speakers_with_data[i]->has_contributed_.load(std::memory_order_acquire)) {
speakers_with_data[i]->playback_delay_frames_.store(current_pipeline_frames, std::memory_order_release);
speakers_with_data[i]->has_contributed_.store(true, std::memory_order_release);
}
speakers_with_data[i]->pending_playback_frames_.fetch_add(frames_to_mix, std::memory_order_release);
transfer_buffers_with_data[i]->decrease_buffer_length(
speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix));
}
// Update output transfer buffer length and pipeline frame count (once, not per source)
output_transfer_buffer->increase_buffer_length(
this_mixer->audio_stream_info_.value().frames_to_bytes(frames_to_mix));
this_mixer->frames_in_pipeline_.fetch_add(frames_to_mix, std::memory_order_release);
}
}
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_STOPPING);
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_STOPPING);
}
// Reset pipeline frame count since the task is stopping
this_mixer->frames_in_pipeline_.store(0, std::memory_order_release);
output_transfer_buffer.reset();
xEventGroupSetBits(this_mixer->event_group_, MIXER_TASK_STATE_STOPPED);
vTaskSuspend(nullptr); // Suspend this task indefinitely until the loop method deletes it
+83
View File
@@ -0,0 +1,83 @@
import esphome.codegen as cg
modbus_ns = cg.esphome_ns.namespace("modbus")
modbus_helpers_ns = modbus_ns.namespace("helpers")
ModbusFunctionCode_ns = modbus_ns.namespace("ModbusFunctionCode")
ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode")
MODBUS_FUNCTION_CODE = {
"read_coils": ModbusFunctionCode.READ_COILS,
"read_discrete_inputs": ModbusFunctionCode.READ_DISCRETE_INPUTS,
"read_holding_registers": ModbusFunctionCode.READ_HOLDING_REGISTERS,
"read_input_registers": ModbusFunctionCode.READ_INPUT_REGISTERS,
"write_single_coil": ModbusFunctionCode.WRITE_SINGLE_COIL,
"write_single_register": ModbusFunctionCode.WRITE_SINGLE_REGISTER,
"write_multiple_coils": ModbusFunctionCode.WRITE_MULTIPLE_COILS,
"write_multiple_registers": ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS,
}
ModbusRegisterType_ns = modbus_ns.namespace("ModbusRegisterType")
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
MODBUS_WRITE_REGISTER_TYPE = {
"custom": ModbusRegisterType.CUSTOM,
"coil": ModbusRegisterType.COIL,
"holding": ModbusRegisterType.HOLDING,
}
MODBUS_REGISTER_TYPE = {
**MODBUS_WRITE_REGISTER_TYPE,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"read": ModbusRegisterType.READ,
}
SensorValueType_ns = modbus_helpers_ns.namespace("SensorValueType")
SensorValueType = SensorValueType_ns.enum("SensorValueType")
SENSOR_VALUE_TYPE = {
"RAW": SensorValueType.RAW,
"U_WORD": SensorValueType.U_WORD,
"S_WORD": SensorValueType.S_WORD,
"U_DWORD": SensorValueType.U_DWORD,
"U_DWORD_R": SensorValueType.U_DWORD_R,
"S_DWORD": SensorValueType.S_DWORD,
"S_DWORD_R": SensorValueType.S_DWORD_R,
"U_QWORD": SensorValueType.U_QWORD,
"U_QWORD_R": SensorValueType.U_QWORD_R,
"S_QWORD": SensorValueType.S_QWORD,
"S_QWORD_R": SensorValueType.S_QWORD_R,
"FP32": SensorValueType.FP32,
"FP32_R": SensorValueType.FP32_R,
}
TYPE_REGISTER_MAP = {
"RAW": 1,
"U_WORD": 1,
"S_WORD": 1,
"U_DWORD": 2,
"U_DWORD_R": 2,
"S_DWORD": 2,
"S_DWORD_R": 2,
"U_QWORD": 4,
"U_QWORD_R": 4,
"S_QWORD": 4,
"S_QWORD_R": 4,
"FP32": 2,
"FP32_R": 2,
}
CPP_TYPE_REGISTER_MAP = {
"RAW": cg.uint16,
"U_WORD": cg.uint16,
"S_WORD": cg.int16,
"U_DWORD": cg.uint32,
"U_DWORD_R": cg.uint32,
"S_DWORD": cg.int32,
"S_DWORD_R": cg.int32,
"U_QWORD": cg.uint64,
"U_QWORD_R": cg.uint64,
"S_QWORD": cg.int64,
"S_QWORD_R": cg.int64,
"FP32": cg.float_,
"FP32_R": cg.float_,
}
+1 -1
View File
@@ -313,7 +313,7 @@ void Modbus::send_next_frame_() {
this->last_send_ = millis();
this->tx_buffer_.pop_front();
if (!this->tx_buffer_.empty()) {
ESP_LOGV(TAG, "Write queue contains %" PRIu32 " items.", this->tx_buffer_.size());
ESP_LOGV(TAG, "Write queue contains %zu items.", this->tx_buffer_.size());
}
}
@@ -0,0 +1,139 @@
#include "modbus_helpers.h"
#include "esphome/core/log.h"
namespace esphome::modbus::helpers {
static const char *const TAG = "modbus_helpers";
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
switch (value_type) {
case SensorValueType::U_WORD:
case SensorValueType::S_WORD:
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD:
case SensorValueType::S_DWORD:
case SensorValueType::FP32:
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::S_DWORD_R:
case SensorValueType::FP32_R:
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
break;
case SensorValueType::U_QWORD:
case SensorValueType::S_QWORD:
data.push_back((value & 0xFFFF000000000000) >> 48);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_QWORD_R:
case SensorValueType::S_QWORD_R:
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF000000000000) >> 48);
break;
default:
ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversion: %d", static_cast<uint16_t>(value_type));
break;
}
}
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask) {
int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
if (offset > data.size()) {
ESP_LOGE(TAG, "not enough data for value");
return value;
}
size_t size = data.size() - offset;
bool error = false;
switch (sensor_value_type) {
case SensorValueType::U_WORD:
if (size >= 2) {
value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset),
bitmask); // default is 0xFFFF ;
} else {
error = true;
}
break;
case SensorValueType::U_DWORD:
case SensorValueType::FP32:
if (size >= 4) {
value = get_data<uint32_t>(data, offset);
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
} else {
error = true;
}
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::FP32_R:
if (size >= 4) {
value = get_data<uint32_t>(data, offset);
value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
} else {
error = true;
}
break;
case SensorValueType::S_WORD:
if (size >= 2) {
value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
bitmask); // default is 0xFFFF ;
} else {
error = true;
}
break;
case SensorValueType::S_DWORD:
if (size >= 4) {
value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
} else {
error = true;
}
break;
case SensorValueType::S_DWORD_R: {
if (size >= 4) {
value = get_data<uint32_t>(data, offset);
// Currently the high word is at the low position
// the sign bit is therefore at low before the switch
uint32_t sign_bit = (value & 0x8000) << 16;
value = mask_and_shift_by_rightbit(
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
} else {
error = true;
}
} break;
case SensorValueType::U_QWORD:
case SensorValueType::S_QWORD:
// Ignore bitmask for QWORD
if (size >= 8) {
value = get_data<uint64_t>(data, offset);
} else {
error = true;
}
break;
case SensorValueType::U_QWORD_R:
case SensorValueType::S_QWORD_R: {
// Ignore bitmask for QWORD
if (size >= 8) {
uint64_t tmp = get_data<uint64_t>(data, offset);
value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
} else {
error = true;
}
} break;
case SensorValueType::RAW:
default:
break;
}
if (error)
ESP_LOGE(TAG, "not enough data for value");
return value;
}
} // namespace esphome::modbus::helpers
+207
View File
@@ -0,0 +1,207 @@
#pragma once
#include <string>
#include <vector>
#include <cmath>
#include "esphome/core/helpers.h"
#include "esphome/components/modbus/modbus_definitions.h"
namespace esphome::modbus::helpers {
enum class SensorValueType : uint8_t {
RAW = 0x00, // variable length
U_WORD = 0x1, // 1 Register unsigned
U_DWORD = 0x2, // 2 Registers unsigned
S_WORD = 0x3, // 1 Register signed
S_DWORD = 0x4, // 2 Registers signed
BIT = 0x5,
U_DWORD_R = 0x6, // 2 Registers unsigned
S_DWORD_R = 0x7, // 2 Registers unsigned
U_QWORD = 0x8,
S_QWORD = 0x9,
U_QWORD_R = 0xA,
S_QWORD_R = 0xB,
FP32 = 0xC,
FP32_R = 0xD
};
inline bool value_type_is_float(SensorValueType v) {
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
}
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::READ_COILS;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::READ_DISCRETE_INPUTS;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_HOLDING_REGISTERS;
case ModbusRegisterType::READ:
return ModbusFunctionCode::READ_INPUT_REGISTERS;
default:
return ModbusFunctionCode::CUSTOM;
}
}
inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::WRITE_SINGLE_COIL;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::CUSTOM;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS;
case ModbusRegisterType::READ:
default:
return ModbusFunctionCode::CUSTOM;
}
}
inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); }
/** Get a byte from a hex string
* byte_from_hex_str("1122", 1) returns uint_8 value 0x22 == 34
* byte_from_hex_str("1122", 0) returns 0x11
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return byte value
*/
inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) {
if (value.length() < pos * 2 + 2)
return 0;
return (c_to_hex(value[pos * 2]) << 4) | c_to_hex(value[pos * 2 + 1]);
}
/** Get a word from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return word value
*/
inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) {
return byte_from_hex_str(value, pos) << 8 | byte_from_hex_str(value, pos + 1);
}
/** Get a dword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return dword value
*/
inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) {
return word_from_hex_str(value, pos) << 16 | word_from_hex_str(value, pos + 2);
}
/** Get a qword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return qword value
*/
inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) {
return static_cast<uint64_t>(dword_from_hex_str(value, pos)) << 32 | dword_from_hex_str(value, pos + 4);
}
// Extract data from modbus response buffer
/** Extract data from modbus response buffer
* @param T one of supported integer data types int_8,int_16,int_32,int_64
* @param data modbus response buffer (uint8_t)
* @param buffer_offset offset in bytes.
* @return value of type T extracted from buffer
*/
template<typename T> T get_data(const std::vector<uint8_t> &data, size_t buffer_offset) {
if (sizeof(T) == sizeof(uint8_t)) {
return T(data[buffer_offset]);
}
if (sizeof(T) == sizeof(uint16_t)) {
return T((uint16_t(data[buffer_offset + 0]) << 8) | (uint16_t(data[buffer_offset + 1]) << 0));
}
if (sizeof(T) == sizeof(uint32_t)) {
return static_cast<uint32_t>(get_data<uint16_t>(data, buffer_offset)) << 16 |
static_cast<uint32_t>(get_data<uint16_t>(data, buffer_offset + 2));
}
if (sizeof(T) == sizeof(uint64_t)) {
return static_cast<uint64_t>(get_data<uint32_t>(data, buffer_offset)) << 32 |
(static_cast<uint64_t>(get_data<uint32_t>(data, buffer_offset + 4)));
}
static_assert(sizeof(T) == sizeof(uint8_t) || sizeof(T) == sizeof(uint16_t) || sizeof(T) == sizeof(uint32_t) ||
sizeof(T) == sizeof(uint64_t),
"Unsupported type size in get_data; only 1, 2, 4, or 8-byte integer types are supported.");
return T{};
}
/** Extract coil data from modbus response buffer
* Responses for coil are packed into bytes .
* coil 3 is bit 3 of the first response byte
* coil 9 is bit 2 of the second response byte
* @param coil number of the cil
* @param data modbus response buffer (uint8_t)
* @return content of coil register
*/
inline bool coil_from_vector(int coil, const std::vector<uint8_t> &data) {
auto data_byte = coil / 8;
return (data[data_byte] & (1 << (coil % 8))) > 0;
}
/** Extract bits from value and shift right according to the bitmask
* if the bitmask is 0x00F0 we want the values frrom bit 5 - 8.
* the result is then shifted right by the position if the first right set bit in the mask
* Useful for modbus data where more than one value is packed in a 16 bit register
* Example: on Epever the "Length of night" register 0x9065 encodes values of the whole night length of time as
* D15 - D8 = hour, D7 - D0 = minute
* To get the hours use mask 0xFF00 and 0x00FF for the minute
* @param data an integral value between 16 aand 32 bits,
* @param bitmask the bitmask to apply
*/
template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
auto result = (mask & data);
if (result == 0 || mask == 0xFFFFFFFF) {
return result;
}
for (size_t pos = 0; pos < sizeof(N) << 3; pos++) {
if (pos < 32 && (mask & (1UL << pos)) != 0)
return result >> pos;
}
return 0;
}
/** Convert float value to vector<uint16_t> suitable for sending
* @param data target for payload
* @param value float value to convert
* @param value_type defines if 16/32 or FP32 is used
* @return vector containing the modbus register words in correct order
*/
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type);
/** Convert vector<uint8_t> response payload to number.
* @param data payload with the data to convert
* @param sensor_value_type defines if 16/32/64 bits or FP32 is used
* @param offset offset to the data in data
* @param bitmask bitmask used for masking and shifting
* @return 64-bit number of the payload
*/
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask);
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
int64_t val;
if (value_type_is_float(value_type)) {
val = bit_cast<uint32_t>(value);
} else {
val = llroundf(value);
}
std::vector<uint16_t> data;
number_to_payload(data, val, value_type);
return data;
}
} // namespace esphome::modbus::helpers
@@ -4,6 +4,13 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import modbus
from esphome.components.const import CONF_ENABLED
from esphome.components.modbus.helpers import (
CPP_TYPE_REGISTER_MAP,
MODBUS_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
TYPE_REGISTER_MAP,
ModbusRegisterType,
)
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_OFFSET
from esphome.cpp_helpers import logging
@@ -41,7 +48,6 @@ CONF_SERVER_REGISTERS = "server_registers"
MULTI_CONF = True
modbus_controller_ns = cg.esphome_ns.namespace("modbus_controller")
modbus_ns = cg.esphome_ns.namespace("modbus")
ModbusController = modbus_controller_ns.class_(
"ModbusController", cg.PollingComponent, modbus.ModbusDevice
)
@@ -50,85 +56,6 @@ SensorItem = modbus_controller_ns.struct("SensorItem")
ServerCourtesyResponse = modbus_controller_ns.struct("ServerCourtesyResponse")
ServerRegister = modbus_controller_ns.struct("ServerRegister")
ModbusFunctionCode_ns = modbus_ns.namespace("ModbusFunctionCode")
ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode")
MODBUS_FUNCTION_CODE = {
"read_coils": ModbusFunctionCode.READ_COILS,
"read_discrete_inputs": ModbusFunctionCode.READ_DISCRETE_INPUTS,
"read_holding_registers": ModbusFunctionCode.READ_HOLDING_REGISTERS,
"read_input_registers": ModbusFunctionCode.READ_INPUT_REGISTERS,
"write_single_coil": ModbusFunctionCode.WRITE_SINGLE_COIL,
"write_single_register": ModbusFunctionCode.WRITE_SINGLE_REGISTER,
"write_multiple_coils": ModbusFunctionCode.WRITE_MULTIPLE_COILS,
"write_multiple_registers": ModbusFunctionCode.WRITE_MULTIPLE_REGISTERS,
}
ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType")
ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType")
MODBUS_WRITE_REGISTER_TYPE = {
"custom": ModbusRegisterType.CUSTOM,
"coil": ModbusRegisterType.COIL,
"holding": ModbusRegisterType.HOLDING,
}
MODBUS_REGISTER_TYPE = {
**MODBUS_WRITE_REGISTER_TYPE,
"discrete_input": ModbusRegisterType.DISCRETE_INPUT,
"read": ModbusRegisterType.READ,
}
SensorValueType_ns = modbus_controller_ns.namespace("SensorValueType")
SensorValueType = SensorValueType_ns.enum("SensorValueType")
SENSOR_VALUE_TYPE = {
"RAW": SensorValueType.RAW,
"U_WORD": SensorValueType.U_WORD,
"S_WORD": SensorValueType.S_WORD,
"U_DWORD": SensorValueType.U_DWORD,
"U_DWORD_R": SensorValueType.U_DWORD_R,
"S_DWORD": SensorValueType.S_DWORD,
"S_DWORD_R": SensorValueType.S_DWORD_R,
"U_QWORD": SensorValueType.U_QWORD,
"U_QWORD_R": SensorValueType.U_QWORD_R,
"S_QWORD": SensorValueType.S_QWORD,
"S_QWORD_R": SensorValueType.S_QWORD_R,
"FP32": SensorValueType.FP32,
"FP32_R": SensorValueType.FP32_R,
}
TYPE_REGISTER_MAP = {
"RAW": 1,
"U_WORD": 1,
"S_WORD": 1,
"U_DWORD": 2,
"U_DWORD_R": 2,
"S_DWORD": 2,
"S_DWORD_R": 2,
"U_QWORD": 4,
"U_QWORD_R": 4,
"S_QWORD": 4,
"S_QWORD_R": 4,
"FP32": 2,
"FP32_R": 2,
}
CPP_TYPE_REGISTER_MAP = {
"RAW": cg.uint16,
"U_WORD": cg.uint16,
"S_WORD": cg.int16,
"U_DWORD": cg.uint32,
"U_DWORD_R": cg.uint32,
"S_DWORD": cg.int32,
"S_DWORD_R": cg.int32,
"U_QWORD": cg.uint64,
"U_QWORD_R": cg.uint64,
"S_QWORD": cg.int64,
"S_QWORD_R": cg.int64,
"FP32": cg.float_,
"FP32_R": cg.float_,
}
_LOGGER = logging.getLogger(__name__)
SERVER_COURTESY_RESPONSE_SCHEMA = cv.Schema(
@@ -1,10 +1,10 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
from esphome.components.modbus.helpers import MODBUS_REGISTER_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from .. import (
MODBUS_REGISTER_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,
@@ -15,10 +15,10 @@ void ModbusBinarySensor::parse_and_publish(const std::vector<uint8_t> &data) {
case ModbusRegisterType::DISCRETE_INPUT:
case ModbusRegisterType::COIL:
// offset for coil is the actual number of the coil not the byte offset
value = coil_from_vector(this->offset, data);
value = modbus::helpers::coil_from_vector(this->offset, data);
break;
default:
value = get_data<uint16_t>(data, this->offset) & this->bitmask;
value = modbus::helpers::get_data<uint16_t>(data, this->offset) & this->bitmask;
break;
}
// Is there a lambda registered
@@ -140,7 +140,7 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
std::vector<uint16_t> payload;
payload.reserve(server_register->register_count * 2);
number_to_payload(payload, value, server_register->value_type);
modbus::helpers::number_to_payload(payload, value, server_register->value_type);
sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
current_address += server_register->register_count;
found = true;
@@ -258,7 +258,7 @@ void ModbusController::on_modbus_write_registers(uint8_t function_code, const st
// Actually write to the registers:
if (!for_each_register([&data](ServerRegister *server_register, uint16_t offset) {
int64_t number = payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
int64_t number = modbus::helpers::payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
return server_register->write_lambda(number);
})) {
this->send_error(function_code, ModbusExceptionCode::SERVICE_DEVICE_FAILURE);
@@ -517,7 +517,8 @@ void ModbusController::loop() {
void ModbusController::on_write_register_response(ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) {
ESP_LOGV(TAG, "Command ACK 0x%X %d ", get_data<uint16_t>(data, 0), get_data<int16_t>(data, 1));
ESP_LOGV(TAG, "Command ACK 0x%X %d ", modbus::helpers::get_data<uint16_t>(data, 0),
modbus::helpers::get_data<int16_t>(data, 1));
}
void ModbusController::dump_sensors_() {
@@ -535,7 +536,7 @@ ModbusCommandItem ModbusCommandItem::create_read_command(
ModbusCommandItem cmd;
cmd.modbusdevice = modbusdevice;
cmd.register_type = register_type;
cmd.function_code = modbus_register_read_function(register_type);
cmd.function_code = modbus::helpers::modbus_register_read_function(register_type);
cmd.register_address = start_address;
cmd.register_count = register_count;
cmd.on_data_func = std::move(handler);
@@ -548,7 +549,7 @@ ModbusCommandItem ModbusCommandItem::create_read_command(ModbusController *modbu
ModbusCommandItem cmd;
cmd.modbusdevice = modbusdevice;
cmd.register_type = register_type;
cmd.function_code = modbus_register_read_function(register_type);
cmd.function_code = modbus::helpers::modbus_register_read_function(register_type);
cmd.register_address = start_address;
cmd.register_count = register_count;
cmd.on_data_func = [modbusdevice](ModbusRegisterType register_type, uint16_t start_address,
@@ -710,132 +711,5 @@ bool ModbusCommandItem::is_equal(const ModbusCommandItem &other) {
other.register_type == this->register_type && other.function_code == this->function_code;
}
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
switch (value_type) {
case SensorValueType::U_WORD:
case SensorValueType::S_WORD:
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD:
case SensorValueType::S_DWORD:
case SensorValueType::FP32:
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::S_DWORD_R:
case SensorValueType::FP32_R:
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
break;
case SensorValueType::U_QWORD:
case SensorValueType::S_QWORD:
data.push_back((value & 0xFFFF000000000000) >> 48);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back(value & 0xFFFF);
break;
case SensorValueType::U_QWORD_R:
case SensorValueType::S_QWORD_R:
data.push_back(value & 0xFFFF);
data.push_back((value & 0xFFFF0000) >> 16);
data.push_back((value & 0xFFFF00000000) >> 32);
data.push_back((value & 0xFFFF000000000000) >> 48);
break;
default:
ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d",
static_cast<uint16_t>(value_type));
break;
}
}
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask) {
int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
size_t size = data.size() - offset;
bool error = false;
switch (sensor_value_type) {
case SensorValueType::U_WORD:
if (size >= 2) {
value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
} else {
error = true;
}
break;
case SensorValueType::U_DWORD:
case SensorValueType::FP32:
if (size >= 4) {
value = get_data<uint32_t>(data, offset);
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
} else {
error = true;
}
break;
case SensorValueType::U_DWORD_R:
case SensorValueType::FP32_R:
if (size >= 4) {
value = get_data<uint32_t>(data, offset);
value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
} else {
error = true;
}
break;
case SensorValueType::S_WORD:
if (size >= 2) {
value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
bitmask); // default is 0xFFFF ;
} else {
error = true;
}
break;
case SensorValueType::S_DWORD:
if (size >= 4) {
value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
} else {
error = true;
}
break;
case SensorValueType::S_DWORD_R: {
if (size >= 4) {
value = get_data<uint32_t>(data, offset);
// Currently the high word is at the low position
// the sign bit is therefore at low before the switch
uint32_t sign_bit = (value & 0x8000) << 16;
value = mask_and_shift_by_rightbit(
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
} else {
error = true;
}
} break;
case SensorValueType::U_QWORD:
case SensorValueType::S_QWORD:
// Ignore bitmask for QWORD
if (size >= 8) {
value = get_data<uint64_t>(data, offset);
} else {
error = true;
}
break;
case SensorValueType::U_QWORD_R:
case SensorValueType::S_QWORD_R: {
// Ignore bitmask for QWORD
if (size >= 8) {
uint64_t tmp = get_data<uint64_t>(data, offset);
value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
} else {
error = true;
}
} break;
case SensorValueType::RAW:
default:
break;
}
if (error)
ESP_LOGE(TAG, "not enough data for value");
return value;
}
} // namespace modbus_controller
} // namespace esphome
@@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/modbus/modbus.h"
#include "esphome/components/modbus/modbus_helpers.h"
#include "esphome/core/automation.h"
#include <list>
@@ -19,188 +20,77 @@ class ModbusController;
using modbus::ModbusFunctionCode;
using modbus::ModbusRegisterType;
using modbus::ModbusExceptionCode;
using modbus::helpers::SensorValueType;
enum class SensorValueType : uint8_t {
RAW = 0x00, // variable length
U_WORD = 0x1, // 1 Register unsigned
U_DWORD = 0x2, // 2 Registers unsigned
S_WORD = 0x3, // 1 Register signed
S_DWORD = 0x4, // 2 Registers signed
BIT = 0x5,
U_DWORD_R = 0x6, // 2 Registers unsigned
S_DWORD_R = 0x7, // 2 Registers unsigned
U_QWORD = 0x8,
S_QWORD = 0x9,
U_QWORD_R = 0xA,
S_QWORD_R = 0xB,
FP32 = 0xC,
FP32_R = 0xD
};
inline bool value_type_is_float(SensorValueType v) {
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
}
// Remove before 2026.10.0 — these helpers have moved to modbus::helpers
ESPDEPRECATED("Use modbus::helpers::value_type_is_float() instead. Removed in 2026.10.0", "2026.4.0")
inline bool value_type_is_float(SensorValueType v) { return modbus::helpers::value_type_is_float(v); }
ESPDEPRECATED("Use modbus::helpers::modbus_register_read_function() instead. Removed in 2026.10.0", "2026.4.0")
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::READ_COILS;
break;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::READ_DISCRETE_INPUTS;
break;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_HOLDING_REGISTERS;
break;
case ModbusRegisterType::READ:
return ModbusFunctionCode::READ_INPUT_REGISTERS;
break;
default:
return ModbusFunctionCode::CUSTOM;
break;
}
return modbus::helpers::modbus_register_read_function(reg_type);
}
ESPDEPRECATED("Use modbus::helpers::modbus_register_write_function() instead. Removed in 2026.10.0", "2026.4.0")
inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_type) {
switch (reg_type) {
case ModbusRegisterType::COIL:
return ModbusFunctionCode::WRITE_SINGLE_COIL;
break;
case ModbusRegisterType::DISCRETE_INPUT:
return ModbusFunctionCode::CUSTOM;
break;
case ModbusRegisterType::HOLDING:
return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS;
break;
case ModbusRegisterType::READ:
default:
return ModbusFunctionCode::CUSTOM;
break;
}
return modbus::helpers::modbus_register_write_function(reg_type);
}
inline uint8_t c_to_hex(char c) { return (c >= 'A') ? (c >= 'a') ? (c - 'a' + 10) : (c - 'A' + 10) : (c - '0'); }
ESPDEPRECATED("Use modbus::helpers::c_to_hex() instead. Removed in 2026.10.0", "2026.4.0")
inline uint8_t c_to_hex(char c) { return modbus::helpers::c_to_hex(c); }
/** Get a byte from a hex string
* hex_byte_from_str("1122",1) returns uint_8 value 0x22 == 34
* hex_byte_from_str("1122",0) returns 0x11
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return byte value
*/
ESPDEPRECATED("Use modbus::helpers::byte_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint8_t byte_from_hex_str(const std::string &value, uint8_t pos) {
if (value.length() < pos * 2 + 1)
return 0;
return (c_to_hex(value[pos * 2]) << 4) | c_to_hex(value[pos * 2 + 1]);
return modbus::helpers::byte_from_hex_str(value, pos);
}
/** Get a word from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return word value
*/
ESPDEPRECATED("Use modbus::helpers::word_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint16_t word_from_hex_str(const std::string &value, uint8_t pos) {
return byte_from_hex_str(value, pos) << 8 | byte_from_hex_str(value, pos + 1);
return modbus::helpers::word_from_hex_str(value, pos);
}
/** Get a dword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return dword value
*/
ESPDEPRECATED("Use modbus::helpers::dword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint32_t dword_from_hex_str(const std::string &value, uint8_t pos) {
return word_from_hex_str(value, pos) << 16 | word_from_hex_str(value, pos + 2);
return modbus::helpers::dword_from_hex_str(value, pos);
}
/** Get a qword from a hex string
* @param value string containing hex encoding
* @param position offset in bytes. Because each byte is encoded in 2 hex digits the position of the original byte in
* the hex string is byte_pos * 2
* @return qword value
*/
ESPDEPRECATED("Use modbus::helpers::qword_from_hex_str() instead. Removed in 2026.10.0", "2026.4.0")
inline uint64_t qword_from_hex_str(const std::string &value, uint8_t pos) {
return static_cast<uint64_t>(dword_from_hex_str(value, pos)) << 32 | dword_from_hex_str(value, pos + 4);
return modbus::helpers::qword_from_hex_str(value, pos);
}
// Extract data from modbus response buffer
/** Extract data from modbus response buffer
* @param T one of supported integer data types int_8,int_16,int_32,int_64
* @param data modbus response buffer (uint8_t)
* @param buffer_offset offset in bytes.
* @return value of type T extracted from buffer
*/
template<typename T> T get_data(const std::vector<uint8_t> &data, size_t buffer_offset) {
if (sizeof(T) == sizeof(uint8_t)) {
return T(data[buffer_offset]);
}
if (sizeof(T) == sizeof(uint16_t)) {
return T((uint16_t(data[buffer_offset + 0]) << 8) | (uint16_t(data[buffer_offset + 1]) << 0));
}
if (sizeof(T) == sizeof(uint32_t)) {
return get_data<uint16_t>(data, buffer_offset) << 16 | get_data<uint16_t>(data, (buffer_offset + 2));
}
if (sizeof(T) == sizeof(uint64_t)) {
return static_cast<uint64_t>(get_data<uint32_t>(data, buffer_offset)) << 32 |
(static_cast<uint64_t>(get_data<uint32_t>(data, buffer_offset + 4)));
}
template<typename T>
ESPDEPRECATED("Use modbus::helpers::get_data() instead. Removed in 2026.10.0", "2026.4.0")
T get_data(const std::vector<uint8_t> &data, size_t buffer_offset) {
return modbus::helpers::get_data<T>(data, buffer_offset);
}
/** Extract coil data from modbus response buffer
* Responses for coil are packed into bytes .
* coil 3 is bit 3 of the first response byte
* coil 9 is bit 2 of the second response byte
* @param coil number of the cil
* @param data modbus response buffer (uint8_t)
* @return content of coil register
*/
ESPDEPRECATED("Use modbus::helpers::coil_from_vector() instead. Removed in 2026.10.0", "2026.4.0")
inline bool coil_from_vector(int coil, const std::vector<uint8_t> &data) {
auto data_byte = coil / 8;
return (data[data_byte] & (1 << (coil % 8))) > 0;
return modbus::helpers::coil_from_vector(coil, data);
}
/** Extract bits from value and shift right according to the bitmask
* if the bitmask is 0x00F0 we want the values frrom bit 5 - 8.
* the result is then shifted right by the position if the first right set bit in the mask
* Useful for modbus data where more than one value is packed in a 16 bit register
* Example: on Epever the "Length of night" register 0x9065 encodes values of the whole night length of time as
* D15 - D8 = hour, D7 - D0 = minute
* To get the hours use mask 0xFF00 and 0x00FF for the minute
* @param data an integral value between 16 aand 32 bits,
* @param bitmask the bitmask to apply
*/
template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
auto result = (mask & data);
if (result == 0 || mask == 0xFFFFFFFF) {
return result;
}
for (size_t pos = 0; pos < sizeof(N) << 3; pos++) {
if ((mask & (1UL << pos)) != 0)
return result >> pos;
}
return 0;
template<typename N>
ESPDEPRECATED("Use modbus::helpers::mask_and_shift_by_rightbit() instead. Removed in 2026.10.0", "2026.4.0")
N mask_and_shift_by_rightbit(N data, uint32_t mask) {
return modbus::helpers::mask_and_shift_by_rightbit(data, mask);
}
/** Convert float value to vector<uint16_t> suitable for sending
* @param data target for payload
* @param value float value to convert
* @param value_type defines if 16/32 or FP32 is used
* @return vector containing the modbus register words in correct order
*/
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type);
ESPDEPRECATED("Use modbus::helpers::number_to_payload() instead. Removed in 2026.10.0", "2026.4.0")
inline void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
modbus::helpers::number_to_payload(data, value, value_type);
}
/** Convert vector<uint8_t> response payload to number.
* @param data payload with the data to convert
* @param sensor_value_type defines if 16/32/64 bits or FP32 is used
* @param offset offset to the data in data
* @param bitmask bitmask used for masking and shifting
* @return 64-bit number of the payload
*/
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask);
ESPDEPRECATED("Use modbus::helpers::payload_to_number() instead. Removed in 2026.10.0", "2026.4.0")
inline int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
uint32_t bitmask) {
return modbus::helpers::payload_to_number(data, sensor_value_type, offset, bitmask);
}
ESPDEPRECATED("Use modbus::helpers::float_to_payload() instead. Removed in 2026.10.0", "2026.4.0")
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
return modbus::helpers::float_to_payload(value, value_type);
}
class ModbusController;
@@ -582,10 +472,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
* @return float value of data
*/
inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem &item) {
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
int64_t number = modbus::helpers::payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
float float_value;
if (value_type_is_float(item.sensor_value_type)) {
if (modbus::helpers::value_type_is_float(item.sensor_value_type)) {
float_value = bit_cast<float>(static_cast<uint32_t>(number));
} else {
float_value = static_cast<float>(number);
@@ -594,19 +484,5 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
return float_value;
}
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
int64_t val;
if (value_type_is_float(value_type)) {
val = bit_cast<uint32_t>(value);
} else {
val = llroundf(value);
}
std::vector<uint16_t> data;
number_to_payload(data, val, value_type);
return data;
}
} // namespace modbus_controller
} // namespace esphome
@@ -1,5 +1,9 @@
import esphome.codegen as cg
from esphome.components import number
from esphome.components.modbus.helpers import (
MODBUS_WRITE_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
@@ -11,8 +15,6 @@ from esphome.const import (
)
from .. import (
MODBUS_WRITE_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,
@@ -62,7 +62,7 @@ void ModbusNumber::control(float value) {
this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data);
});
} else {
data = float_to_payload(write_value, this->sensor_value_type);
data = modbus::helpers::float_to_payload(write_value, this->sensor_value_type);
ESP_LOGD(TAG,
"Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
@@ -1,10 +1,10 @@
import esphome.codegen as cg
from esphome.components import output
from esphome.components.modbus.helpers import SENSOR_VALUE_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_MULTIPLY
from .. import (
SENSOR_VALUE_TYPE,
ModbusItemBaseSchema,
SensorItem,
modbus_calc_properties,
@@ -34,7 +34,7 @@ void ModbusFloatOutput::write_state(float value) {
}
// lambda didn't set payload
if (data.empty()) {
data = float_to_payload(value, this->sensor_value_type);
data = modbus::helpers::float_to_payload(value, this->sensor_value_type);
}
ESP_LOGD(TAG, "Updating register: start address=0x%X register count=%d new value=%.02f (val=%.02f)",
@@ -1,15 +1,10 @@
import esphome.codegen as cg
from esphome.components import select
from esphome.components.modbus.helpers import SENSOR_VALUE_TYPE, TYPE_REGISTER_MAP
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC
from .. import (
SENSOR_VALUE_TYPE,
TYPE_REGISTER_MAP,
ModbusController,
SensorItem,
modbus_controller_ns,
)
from .. import ModbusController, SensorItem, modbus_controller_ns
from ..const import (
CONF_FORCE_NEW_RANGE,
CONF_MODBUS_CONTROLLER_ID,
@@ -9,7 +9,7 @@ static const char *const TAG = "modbus_controller.select";
void ModbusSelect::dump_config() { LOG_SELECT(TAG, "Modbus Controller Select", this); }
void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
int64_t value = payload_to_number(data, this->sensor_value_type, this->offset, this->bitmask);
int64_t value = modbus::helpers::payload_to_number(data, this->sensor_value_type, this->offset, this->bitmask);
ESP_LOGD(TAG, "New select value %lld from payload", value);
@@ -61,7 +61,7 @@ void ModbusSelect::control(size_t index) {
}
if (data.empty()) {
number_to_payload(data, *mapval, this->sensor_value_type);
modbus::helpers::number_to_payload(data, *mapval, this->sensor_value_type);
} else {
ESP_LOGV(TAG, "Using payload from write lambda");
}
@@ -1,11 +1,10 @@
import esphome.codegen as cg
from esphome.components import sensor
from esphome.components.modbus.helpers import MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from .. import (
MODBUS_REGISTER_TYPE,
SENSOR_VALUE_TYPE,
ModbusItemBaseSchema,
SensorItem,
add_modbus_base_properties,

Some files were not shown because too many files have changed in this diff Show More