Decode DP127 as 48 bytes (4 alarms), plus DP128, DP107 and DP126, from Tuya's data model. Add day switches, alarm and sunset controls, a knock sensor on PA18, display schedule, bedtime fade, radio timer and grouped web UI. Pin LibreTiny v1.14.0 for the OTA restart fix. Ship the patched UART as an external component so a server needs only a short YAML. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
219 lines
6.6 KiB
C++
219 lines
6.6 KiB
C++
#ifdef USE_RP2
|
|
#include "uart_component_rp2.h"
|
|
#include "esphome/core/application.h"
|
|
#include "esphome/core/defines.h"
|
|
#include "esphome/core/helpers.h"
|
|
#include "esphome/core/log.h"
|
|
|
|
#include <hardware/uart.h>
|
|
|
|
#ifdef USE_LOGGER
|
|
#include "esphome/components/logger/logger.h"
|
|
#endif
|
|
|
|
namespace esphome::uart {
|
|
|
|
static const char *const TAG = "uart";
|
|
|
|
uint16_t RP2UartComponent::get_config() {
|
|
uint16_t config = 0;
|
|
|
|
if (this->parity_ == UART_CONFIG_PARITY_NONE) {
|
|
config |= UART_PARITY_NONE;
|
|
} else if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
|
|
config |= UART_PARITY_EVEN;
|
|
} else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
|
|
config |= UART_PARITY_ODD;
|
|
}
|
|
|
|
switch (this->data_bits_) {
|
|
case 5:
|
|
config |= SERIAL_DATA_5;
|
|
break;
|
|
case 6:
|
|
config |= SERIAL_DATA_6;
|
|
break;
|
|
case 7:
|
|
config |= SERIAL_DATA_7;
|
|
break;
|
|
case 8:
|
|
config |= SERIAL_DATA_8;
|
|
break;
|
|
}
|
|
|
|
if (this->stop_bits_ == 1) {
|
|
config |= SERIAL_STOP_BIT_1;
|
|
} else {
|
|
config |= SERIAL_STOP_BIT_2;
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
void RP2UartComponent::setup() {
|
|
auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
|
|
if (!pin) {
|
|
return;
|
|
}
|
|
const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN;
|
|
if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
|
|
pin->setup();
|
|
}
|
|
};
|
|
|
|
setup_pin_if_needed(this->rx_pin_);
|
|
if (this->rx_pin_ != this->tx_pin_) {
|
|
setup_pin_if_needed(this->tx_pin_);
|
|
}
|
|
|
|
uint16_t config = get_config();
|
|
|
|
constexpr uint32_t valid_tx_uart_0 = __bitset({0, 12, 16, 28});
|
|
constexpr uint32_t valid_tx_uart_1 = __bitset({4, 8, 20, 24});
|
|
|
|
constexpr uint32_t valid_rx_uart_0 = __bitset({1, 13, 17, 29});
|
|
constexpr uint32_t valid_rx_uart_1 = __bitset({5, 9, 21, 25});
|
|
|
|
int8_t tx_hw = -1;
|
|
int8_t rx_hw = -1;
|
|
|
|
if (this->tx_pin_ != nullptr) {
|
|
if (this->tx_pin_->is_inverted()) {
|
|
ESP_LOGD(TAG, "An inverted TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin());
|
|
} else {
|
|
if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_0) != 0) {
|
|
tx_hw = 0;
|
|
} else if (((1 << this->tx_pin_->get_pin()) & valid_tx_uart_1) != 0) {
|
|
tx_hw = 1;
|
|
} else {
|
|
ESP_LOGD(TAG, "TX pin %u can only be used with SerialPIO", this->tx_pin_->get_pin());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this->rx_pin_ != nullptr) {
|
|
if (this->rx_pin_->is_inverted()) {
|
|
ESP_LOGD(TAG, "An inverted RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin());
|
|
} else {
|
|
if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_0) != 0) {
|
|
rx_hw = 0;
|
|
} else if (((1 << this->rx_pin_->get_pin()) & valid_rx_uart_1) != 0) {
|
|
rx_hw = 1;
|
|
} else {
|
|
ESP_LOGD(TAG, "RX pin %u can only be used with SerialPIO", this->rx_pin_->get_pin());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine which hardware UART to use. A pin that is not specified
|
|
// should not prevent hardware UART selection — one-way UART is valid.
|
|
// When both pins are configured, both must be HW-capable and agree on UART number.
|
|
// When only one pin is configured (nullptr other), use that pin's HW UART.
|
|
// If a pin is configured but not HW-capable (inverted/invalid), fall back to SerialPIO.
|
|
int8_t hw_uart = -1;
|
|
const bool tx_configured = (this->tx_pin_ != nullptr);
|
|
const bool rx_configured = (this->rx_pin_ != nullptr);
|
|
|
|
if (tx_configured && rx_configured) {
|
|
// Both pins configured — both must map to the same hardware UART
|
|
if (tx_hw != -1 && rx_hw != -1 && tx_hw == rx_hw) {
|
|
hw_uart = tx_hw;
|
|
}
|
|
} else if (tx_configured) {
|
|
hw_uart = tx_hw;
|
|
} else if (rx_configured) {
|
|
hw_uart = rx_hw;
|
|
}
|
|
|
|
#ifdef USE_LOGGER
|
|
if (hw_uart != -1 && logger::global_logger->get_uart() == hw_uart) {
|
|
ESP_LOGD(TAG, "Using SerialPIO as UART%d is taken by the logger", hw_uart);
|
|
hw_uart = -1;
|
|
}
|
|
#endif
|
|
|
|
if (hw_uart == -1) {
|
|
ESP_LOGV(TAG, "Using SerialPIO");
|
|
pin_size_t tx = this->tx_pin_ == nullptr ? NOPIN : this->tx_pin_->get_pin();
|
|
pin_size_t rx = this->rx_pin_ == nullptr ? NOPIN : this->rx_pin_->get_pin();
|
|
auto *serial = new SerialPIO(tx, rx, this->rx_buffer_size_); // NOLINT(cppcoreguidelines-owning-memory)
|
|
serial->begin(this->baud_rate_, config);
|
|
if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted())
|
|
gpio_set_outover(tx, GPIO_OVERRIDE_INVERT);
|
|
if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted())
|
|
gpio_set_inover(rx, GPIO_OVERRIDE_INVERT);
|
|
this->serial_ = serial;
|
|
} else {
|
|
ESP_LOGV(TAG, "Using Hardware Serial");
|
|
SerialUART *serial;
|
|
if (hw_uart == 0) {
|
|
serial = &Serial1;
|
|
} else {
|
|
serial = &Serial2;
|
|
}
|
|
if (this->tx_pin_ != nullptr)
|
|
serial->setTX(this->tx_pin_->get_pin());
|
|
if (this->rx_pin_ != nullptr)
|
|
serial->setRX(this->rx_pin_->get_pin());
|
|
serial->setFIFOSize(this->rx_buffer_size_);
|
|
serial->begin(this->baud_rate_, config);
|
|
this->serial_ = serial;
|
|
this->hw_serial_ = true;
|
|
}
|
|
}
|
|
|
|
void RP2UartComponent::dump_config() {
|
|
ESP_LOGCONFIG(TAG, "UART Bus:");
|
|
LOG_PIN(" TX Pin: ", tx_pin_);
|
|
LOG_PIN(" RX Pin: ", rx_pin_);
|
|
if (this->rx_pin_ != nullptr) {
|
|
ESP_LOGCONFIG(TAG, " RX Buffer Size: %u", this->rx_buffer_size_);
|
|
}
|
|
ESP_LOGCONFIG(TAG,
|
|
" Baud Rate: %u baud\n"
|
|
" Data Bits: %u\n"
|
|
" Parity: %s\n"
|
|
" Stop bits: %u",
|
|
this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
|
|
if (this->hw_serial_) {
|
|
ESP_LOGCONFIG(TAG, " Using hardware serial");
|
|
} else {
|
|
ESP_LOGCONFIG(TAG, " Using SerialPIO");
|
|
}
|
|
}
|
|
|
|
void RP2UartComponent::write_array(const uint8_t *data, size_t len) {
|
|
this->serial_->write(data, len);
|
|
#ifdef USE_UART_DEBUGGER
|
|
for (size_t i = 0; i < len; i++) {
|
|
this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
|
|
}
|
|
#endif
|
|
}
|
|
bool RP2UartComponent::peek_byte(uint8_t *data) {
|
|
if (!this->check_read_timeout_())
|
|
return false;
|
|
*data = this->serial_->peek();
|
|
return true;
|
|
}
|
|
bool RP2UartComponent::read_array(uint8_t *data, size_t len) {
|
|
if (!this->check_read_timeout_(len))
|
|
return false;
|
|
this->serial_->readBytes(data, len);
|
|
#ifdef USE_UART_DEBUGGER
|
|
for (size_t i = 0; i < len; i++) {
|
|
this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
size_t RP2UartComponent::available() { return this->serial_->available(); }
|
|
UARTFlushResult RP2UartComponent::flush() {
|
|
ESP_LOGVV(TAG, " Flushing");
|
|
this->serial_->flush();
|
|
return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;
|
|
}
|
|
|
|
} // namespace esphome::uart
|
|
#endif // USE_RP2
|