mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 07:17:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcd39aa8ba | ||
|
|
152bab9699 | ||
|
|
a1307f0f40 | ||
|
|
b318499666 |
@@ -30,10 +30,53 @@ using modbus::ModbusFunctionCode;
|
||||
using modbus::ModbusRegisterType;
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// Span overloads of the former modbus_controller helpers: read lambdas receive their payload as a
|
||||
// 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 FunctionCode modbus_register_read_function(modbus::EntityType reg_type) {
|
||||
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 FunctionCode modbus_register_write_function(modbus::EntityType reg_type) {
|
||||
return modbus::helpers::modbus_register_write_function(reg_type);
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
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) {
|
||||
return modbus::helpers::byte_from_hex_str(value, pos);
|
||||
}
|
||||
|
||||
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 modbus::helpers::word_from_hex_str(value, pos);
|
||||
}
|
||||
|
||||
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 modbus::helpers::dword_from_hex_str(value, pos);
|
||||
}
|
||||
|
||||
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 modbus::helpers::qword_from_hex_str(value, pos);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Span overloads of the deprecated helpers below: read lambdas receive their payload as a
|
||||
// std::span<const uint8_t> (previously a const std::vector<uint8_t> &), and a span does not convert to
|
||||
// a vector, so existing lambdas calling these by name need an overload that accepts one. These carry
|
||||
// the 2026.8.0 deprecation window, since the span forms only exist from it.
|
||||
// this release's deprecation window, since the span forms only exist from it.
|
||||
// payload_to_number() deliberately has no such overload: one of its arguments is a modbus::helpers
|
||||
// type, so a span call already reaches the helper by argument-dependent lookup, and a forwarder here
|
||||
// would only make that call ambiguous.
|
||||
@@ -56,6 +99,33 @@ inline bool coil_from_vector(int coil, std::span<const uint8_t> data) {
|
||||
return modbus::helpers::bit_from_packed(coil, data);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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(std::span<const uint8_t>(data), sensor_value_type, offset, bitmask)
|
||||
.value_or(0);
|
||||
}
|
||||
|
||||
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) {
|
||||
std::vector<uint16_t> data;
|
||||
modbus::helpers::float_to_payload(data, value, value_type);
|
||||
return data;
|
||||
}
|
||||
|
||||
class ModbusController;
|
||||
|
||||
/// How an item relates to the register range built just before it (same register type, address order).
|
||||
/// The numeric order doubles as the comparator tiebreak for items at the same address (see
|
||||
/// SensorItemsComparator): AUTO items form the shared range first, so a NEVER item comes last and
|
||||
|
||||
@@ -64,6 +64,9 @@ class RemoteTransmitterComponent final : public remote_base::RemoteTransmitterBa
|
||||
#if defined(USE_ESP32) && SOC_RMT_SUPPORTED
|
||||
void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; }
|
||||
void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; }
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
void loop() override;
|
||||
#endif
|
||||
#endif
|
||||
#if (defined(USE_ESP32) && SOC_RMT_SUPPORTED) || defined(USE_LIBRETINY_VARIANT_RTL8720C) || \
|
||||
defined(REMOTE_TRANSMITTER_BK_PWM)
|
||||
@@ -145,21 +148,32 @@ class RemoteTransmitterComponent final : public remote_base::RemoteTransmitterBa
|
||||
void wait_for_rmt_();
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
static bool tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *event, void *arg);
|
||||
size_t encode_symbols_(rmt_symbol_half_t *out, uint32_t send_wait, uint32_t *offset);
|
||||
void wait_all_done_();
|
||||
void deliver_completion_();
|
||||
|
||||
RemoteTransmitterComponentStore store_{};
|
||||
std::vector<rmt_symbol_half_t> rmt_temp_;
|
||||
#else
|
||||
std::vector<rmt_symbol_word_t> rmt_temp_;
|
||||
#endif
|
||||
uint32_t current_carrier_frequency_{38000};
|
||||
bool initialized_{false};
|
||||
bool with_dma_{false};
|
||||
bool eot_level_{false};
|
||||
rmt_channel_handle_t channel_{NULL};
|
||||
rmt_encoder_handle_t encoder_{NULL};
|
||||
esp_err_t error_code_{ESP_OK};
|
||||
std::string error_string_;
|
||||
bool initialized_{false};
|
||||
bool with_dma_{false};
|
||||
bool eot_level_{false};
|
||||
bool inverted_{false};
|
||||
bool non_blocking_{false};
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
// set when a frame is handed to the hardware, cleared once its completion is reported;
|
||||
// the transmit done interrupt sets tx_done_
|
||||
bool tx_active_{false};
|
||||
volatile bool tx_done_{false};
|
||||
#endif
|
||||
#endif
|
||||
uint8_t carrier_duty_percent_{50};
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ static const char *const TAG = "remote_transmitter";
|
||||
static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
// How long a blocking wait sleeps between watchdog feeds
|
||||
static constexpr int RMT_WAIT_SLICE_MS = 50;
|
||||
|
||||
static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free,
|
||||
rmt_symbol_word_t *symbols, bool *done, void *arg) {
|
||||
auto *store = static_cast<RemoteTransmitterComponentStore *>(arg);
|
||||
@@ -49,6 +52,31 @@ static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size
|
||||
*done = false;
|
||||
return count;
|
||||
}
|
||||
|
||||
// Splits a duration into 15-bit symbols; with out == nullptr only counts them
|
||||
static size_t write_symbols(rmt_symbol_half_t *out, size_t pos, uint32_t ticks, bool level) {
|
||||
size_t count = 0;
|
||||
while (ticks > 0) {
|
||||
uint32_t duration = std::min(ticks, RMT_SYMBOL_DURATION_MAX);
|
||||
if (out != nullptr) {
|
||||
out[pos + count] = {
|
||||
.duration = static_cast<uint16_t>(duration),
|
||||
.level = static_cast<uint16_t>(level),
|
||||
};
|
||||
}
|
||||
ticks -= duration;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool IRAM_ATTR HOT RemoteTransmitterComponent::tx_done_callback(rmt_channel_handle_t channel,
|
||||
const rmt_tx_done_event_data_t *event, void *arg) {
|
||||
auto *self = static_cast<RemoteTransmitterComponent *>(arg);
|
||||
self->tx_done_ = true;
|
||||
self->enable_loop_soon_any_context();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void RemoteTransmitterComponent::setup() {
|
||||
@@ -83,8 +111,12 @@ void RemoteTransmitterComponent::digital_write(bool value) {
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = value;
|
||||
config.flags.queue_nonblocking = 1;
|
||||
// a frame still on the wire finishes first and reports its completion
|
||||
this->wait_for_rmt_();
|
||||
this->store_.times = 1;
|
||||
this->store_.index = 0;
|
||||
rmt_encoder_handle_t encoder = this->encoder_;
|
||||
#else
|
||||
rmt_symbol_word_t symbol = {
|
||||
.duration0 = 1,
|
||||
@@ -95,17 +127,24 @@ void RemoteTransmitterComponent::digital_write(bool value) {
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = value;
|
||||
rmt_encoder_handle_t encoder = this->encoder_;
|
||||
#endif
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
|
||||
esp_err_t error = rmt_transmit(this->channel_, encoder, &symbol, sizeof(symbol), &config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
}
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
this->wait_all_done_();
|
||||
// a level write is not a frame, so its completion is not reported
|
||||
this->tx_done_ = false;
|
||||
#else
|
||||
error = rmt_tx_wait_all_done(this->channel_, -1);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::configure_rmt_() {
|
||||
@@ -152,6 +191,17 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
rmt_tx_event_callbacks_t callbacks;
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.on_trans_done = tx_done_callback;
|
||||
error = rmt_tx_register_event_callbacks(this->channel_, &callbacks, this);
|
||||
if (error != ESP_OK) {
|
||||
this->error_code_ = error;
|
||||
this->error_string_ = "in rmt_tx_register_event_callbacks";
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
rmt_simple_encoder_config_t encoder;
|
||||
memset(&encoder, 0, sizeof(encoder));
|
||||
encoder.callback = encoder_callback;
|
||||
@@ -206,6 +256,118 @@ void RemoteTransmitterComponent::configure_rmt_() {
|
||||
}
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
// Blocks until the hardware is idle, feeding the watchdog while waiting
|
||||
void RemoteTransmitterComponent::wait_all_done_() {
|
||||
esp_err_t error;
|
||||
while ((error = rmt_tx_wait_all_done(this->channel_, RMT_WAIT_SLICE_MS)) == ESP_ERR_TIMEOUT) {
|
||||
App.feed_wdt();
|
||||
}
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::deliver_completion_() {
|
||||
this->tx_done_ = false;
|
||||
this->tx_active_ = false;
|
||||
this->complete_trigger_.trigger();
|
||||
}
|
||||
|
||||
// Blocks until any frame on the wire has gone out and reports its completion
|
||||
void RemoteTransmitterComponent::wait_for_rmt_() {
|
||||
this->wait_all_done_();
|
||||
if (this->tx_active_)
|
||||
this->deliver_completion_();
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::loop() {
|
||||
if (this->tx_done_)
|
||||
this->deliver_completion_();
|
||||
// the transmit done interrupt re-enables the loop
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
// Encodes the repeat gap followed by the frame; with out == nullptr only counts symbols.
|
||||
// The gap leads the buffer so the encoder skips it on the first pass and replays it
|
||||
// before every repeat; offset receives the index of the first frame symbol.
|
||||
size_t RemoteTransmitterComponent::encode_symbols_(rmt_symbol_half_t *out, uint32_t send_wait, uint32_t *offset) {
|
||||
size_t count = write_symbols(out, 0, this->from_microseconds_(send_wait), this->eot_level_);
|
||||
*offset = count;
|
||||
for (int32_t value : this->temp_.get_data()) {
|
||||
bool level = value >= 0;
|
||||
if (!level) {
|
||||
value = -value;
|
||||
}
|
||||
count += write_symbols(out, count, this->from_microseconds_(static_cast<uint32_t>(value)), level ^ this->inverted_);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
if (this->is_failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->tx_active_ && this->tx_done_) {
|
||||
// finished, but loop() has not run yet
|
||||
this->deliver_completion_();
|
||||
}
|
||||
|
||||
if (send_times == 0 || this->tx_active_) {
|
||||
// nothing is sent, but both triggers still fire so an on_complete-sequenced automation
|
||||
// does not stall; a zero repeat count would never finish in the encoder, and a frame that
|
||||
// arrives while another is on the wire is dropped rather than blocking the loop
|
||||
if (this->tx_active_) {
|
||||
ESP_LOGW(TAG, "Transmitter busy, dropping");
|
||||
this->status_set_warning();
|
||||
}
|
||||
this->transmit_trigger_.trigger();
|
||||
this->complete_trigger_.trigger();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) {
|
||||
this->current_carrier_frequency_ = this->temp_.get_carrier_frequency();
|
||||
this->configure_rmt_();
|
||||
}
|
||||
|
||||
uint32_t offset;
|
||||
size_t count = this->encode_symbols_(nullptr, send_wait, &offset);
|
||||
if (count <= offset) {
|
||||
ESP_LOGE(TAG, "Empty data");
|
||||
return;
|
||||
}
|
||||
this->rmt_temp_.resize(count);
|
||||
this->encode_symbols_(this->rmt_temp_.data(), send_wait, &offset);
|
||||
this->store_.times = send_times;
|
||||
this->store_.index = offset;
|
||||
|
||||
this->transmit_trigger_.trigger();
|
||||
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = this->eot_level_;
|
||||
config.flags.queue_nonblocking = 1;
|
||||
this->tx_done_ = false;
|
||||
this->tx_active_ = true;
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
|
||||
this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
// nothing will complete, so report it now
|
||||
this->deliver_completion_();
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
if (!this->non_blocking_) {
|
||||
this->wait_for_rmt_();
|
||||
}
|
||||
}
|
||||
#else
|
||||
void RemoteTransmitterComponent::wait_for_rmt_() {
|
||||
esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1);
|
||||
if (error != ESP_OK) {
|
||||
@@ -216,87 +378,6 @@ void RemoteTransmitterComponent::wait_for_rmt_() {
|
||||
this->complete_trigger_.trigger();
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
uint64_t total_duration = 0;
|
||||
|
||||
if (this->is_failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the timeout was cancelled, block until the tx is complete
|
||||
if (this->non_blocking_ && this->cancel_timeout("complete")) {
|
||||
this->wait_for_rmt_();
|
||||
}
|
||||
|
||||
if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) {
|
||||
this->current_carrier_frequency_ = this->temp_.get_carrier_frequency();
|
||||
this->configure_rmt_();
|
||||
}
|
||||
|
||||
this->rmt_temp_.clear();
|
||||
this->rmt_temp_.reserve(this->temp_.get_data().size() + 1);
|
||||
|
||||
// encode any delay at the start of the buffer to simplify the encoder callback
|
||||
// this will be skipped the first time around
|
||||
total_duration += send_wait * (send_times - 1);
|
||||
send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
|
||||
while (send_wait > 0) {
|
||||
int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
this->rmt_temp_.push_back({
|
||||
.duration = static_cast<uint16_t>(duration),
|
||||
.level = static_cast<uint16_t>(this->eot_level_),
|
||||
});
|
||||
send_wait -= duration;
|
||||
}
|
||||
|
||||
// encode data
|
||||
size_t offset = this->rmt_temp_.size();
|
||||
for (int32_t value : this->temp_.get_data()) {
|
||||
bool level = value >= 0;
|
||||
if (!level) {
|
||||
value = -value;
|
||||
}
|
||||
total_duration += value * send_times;
|
||||
value = this->from_microseconds_(static_cast<uint32_t>(value));
|
||||
while (value > 0) {
|
||||
int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
|
||||
this->rmt_temp_.push_back({
|
||||
.duration = static_cast<uint16_t>(duration),
|
||||
.level = static_cast<uint16_t>(level ^ this->inverted_),
|
||||
});
|
||||
value -= duration;
|
||||
}
|
||||
}
|
||||
|
||||
if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) {
|
||||
ESP_LOGE(TAG, "Empty data");
|
||||
return;
|
||||
}
|
||||
|
||||
this->transmit_trigger_.trigger();
|
||||
|
||||
rmt_transmit_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.flags.eot_level = this->eot_level_;
|
||||
this->store_.times = send_times;
|
||||
this->store_.index = offset;
|
||||
esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
|
||||
this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
|
||||
if (error != ESP_OK) {
|
||||
ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
if (this->non_blocking_) {
|
||||
this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); });
|
||||
} else {
|
||||
this->wait_for_rmt_();
|
||||
}
|
||||
}
|
||||
#else
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user