mirror of
https://github.com/esphome/esphome.git
synced 2026-09-15 17:18:40 +00:00
Merge branch 'dev' into api/peel-first-write-iteration
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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(¤t_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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_()) {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@G-Pereira", "@jesserockz"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
+10
-40
@@ -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},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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_,
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user