mirror of
https://github.com/esphome/esphome.git
synced 2026-01-15 22:44:47 -07:00
Compare commits
35 Commits
integratio
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e01c4f86e | ||
|
|
f4c17e15ea | ||
|
|
d6507ce329 | ||
|
|
9504e92458 | ||
|
|
3911991de2 | ||
|
|
dede47477b | ||
|
|
dca8def0f2 | ||
|
|
a1727a8901 | ||
|
|
b6f3a5d8b7 | ||
|
|
3322b04e00 | ||
|
|
47d0d3cfeb | ||
|
|
8255c02d5d | ||
|
|
8b4ba8dfe6 | ||
|
|
178a61b6fd | ||
|
|
b5df4cdf1d | ||
|
|
d8c23d4fc9 | ||
|
|
e9e0712959 | ||
|
|
062840dd7b | ||
|
|
f0f01c081a | ||
|
|
dd855985be | ||
|
|
5b5cede5f9 | ||
|
|
c737033cc4 | ||
|
|
0194bfd9ea | ||
|
|
339399eb70 | ||
|
|
99f7e9aeb7 | ||
|
|
ebb6babb3d | ||
|
|
0922f240e0 | ||
|
|
c8fb694dcb | ||
|
|
6054685dae | ||
|
|
61ec3508ed | ||
|
|
086ec770ea | ||
|
|
b055f5b4bf | ||
|
|
726db746c8 | ||
|
|
1922455fa7 | ||
|
|
dc943d7e7a |
@@ -1 +1 @@
|
||||
766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07
|
||||
5969e705693278d984c5292e998df0cbaf34f7e1f04dfc7f7b7ad7168527bfa7
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.12.1
|
||||
PROJECT_NUMBER = 2025.12.6
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -11,6 +11,16 @@ FROM base-source-${BUILD_TYPE} AS base
|
||||
|
||||
RUN git config --system --add safe.directory "*"
|
||||
|
||||
# Install build tools for Python packages that require compilation
|
||||
# (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager)
|
||||
RUN if command -v apk > /dev/null; then \
|
||||
apk add --no-cache build-base; \
|
||||
else \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*; \
|
||||
fi
|
||||
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
RUN pip install --no-cache-dir -U pip uv==0.6.14
|
||||
|
||||
@@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||
cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All(
|
||||
cv.frequency, cv.Range(min=45.0, max=66.0)
|
||||
cv.frequency, cv.float_range(min=45.0, max=66.0)
|
||||
),
|
||||
cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema,
|
||||
|
||||
@@ -160,41 +160,63 @@ HYST_LEVEL = {
|
||||
"High": HystLevel.HYST_LEVEL_HIGH,
|
||||
}
|
||||
|
||||
# Config key -> Validator mapping
|
||||
# Optional settings to generate setter calls for
|
||||
CONFIG_MAP = {
|
||||
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0),
|
||||
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False),
|
||||
CONF_DC_BLOCKING_FILTER: cv.boolean,
|
||||
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)),
|
||||
CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
|
||||
CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
|
||||
CONF_CHANNEL: cv.uint8_t,
|
||||
CONF_CHANNEL_SPACING: cv.All(cv.frequency, cv.float_range(min=25000, max=405000)),
|
||||
CONF_FSK_DEVIATION: cv.All(cv.frequency, cv.float_range(min=1500, max=381000)),
|
||||
CONF_MSK_DEVIATION: cv.int_range(min=1, max=8),
|
||||
CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000),
|
||||
CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False),
|
||||
CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean,
|
||||
CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False),
|
||||
CONF_MANCHESTER: cv.boolean,
|
||||
CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7),
|
||||
CONF_SYNC1: cv.hex_uint8_t,
|
||||
CONF_SYNC0: cv.hex_uint8_t,
|
||||
CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False),
|
||||
CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False),
|
||||
CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False),
|
||||
CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7),
|
||||
CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False),
|
||||
CONF_LNA_PRIORITY: cv.boolean,
|
||||
CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False),
|
||||
CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False),
|
||||
CONF_FREEZE: cv.enum(FREEZE, upper=False),
|
||||
CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False),
|
||||
CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False),
|
||||
CONF_PACKET_MODE: cv.boolean,
|
||||
CONF_PACKET_LENGTH: cv.uint8_t,
|
||||
CONF_CRC_ENABLE: cv.boolean,
|
||||
CONF_WHITENING: cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT_POWER, default=10): cv.float_range(min=-30.0, max=11.0),
|
||||
cv.Optional(CONF_RX_ATTENUATION, default="0dB"): cv.enum(
|
||||
RX_ATTENUATION, upper=False
|
||||
),
|
||||
cv.Optional(CONF_DC_BLOCKING_FILTER, default=True): cv.boolean,
|
||||
cv.Optional(CONF_FREQUENCY, default="433.92MHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)
|
||||
),
|
||||
cv.Optional(CONF_IF_FREQUENCY, default="153kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=25000, max=788000)
|
||||
),
|
||||
cv.Optional(CONF_FILTER_BANDWIDTH, default="203kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=58000, max=812000)
|
||||
),
|
||||
cv.Optional(CONF_CHANNEL, default=0): cv.uint8_t,
|
||||
cv.Optional(CONF_CHANNEL_SPACING, default="200kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=25000, max=405000)
|
||||
),
|
||||
cv.Optional(CONF_FSK_DEVIATION): cv.All(
|
||||
cv.frequency, cv.float_range(min=1500, max=381000)
|
||||
),
|
||||
cv.Optional(CONF_MSK_DEVIATION): cv.int_range(min=1, max=8),
|
||||
cv.Optional(CONF_SYMBOL_RATE, default=5000): cv.float_range(min=600, max=500000),
|
||||
cv.Optional(CONF_SYNC_MODE, default="16/16"): cv.enum(SYNC_MODE, upper=False),
|
||||
cv.Optional(CONF_CARRIER_SENSE_ABOVE_THRESHOLD, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MODULATION_TYPE, default="ASK/OOK"): cv.enum(
|
||||
MODULATION, upper=False
|
||||
),
|
||||
cv.Optional(CONF_MANCHESTER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_NUM_PREAMBLE, default=2): cv.int_range(min=0, max=7),
|
||||
cv.Optional(CONF_SYNC1, default=0xD3): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_SYNC0, default=0x91): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_MAGN_TARGET, default="42dB"): cv.enum(MAGN_TARGET, upper=False),
|
||||
cv.Optional(CONF_MAX_LNA_GAIN, default="Default"): cv.enum(
|
||||
MAX_LNA_GAIN, upper=False
|
||||
),
|
||||
cv.Optional(CONF_MAX_DVGA_GAIN, default="-3"): cv.enum(MAX_DVGA_GAIN, upper=False),
|
||||
cv.Optional(CONF_CARRIER_SENSE_ABS_THR): cv.int_range(min=-8, max=7),
|
||||
cv.Optional(CONF_CARRIER_SENSE_REL_THR): cv.enum(
|
||||
CARRIER_SENSE_REL_THR, upper=False
|
||||
),
|
||||
cv.Optional(CONF_LNA_PRIORITY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_FILTER_LENGTH_FSK_MSK): cv.enum(
|
||||
FILTER_LENGTH_FSK_MSK, upper=False
|
||||
),
|
||||
cv.Optional(CONF_FILTER_LENGTH_ASK_OOK): cv.enum(
|
||||
FILTER_LENGTH_ASK_OOK, upper=False
|
||||
),
|
||||
cv.Optional(CONF_FREEZE): cv.enum(FREEZE, upper=False),
|
||||
cv.Optional(CONF_WAIT_TIME, default="32"): cv.enum(WAIT_TIME, upper=False),
|
||||
cv.Optional(CONF_HYST_LEVEL): cv.enum(HYST_LEVEL, upper=False),
|
||||
cv.Optional(CONF_PACKET_MODE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PACKET_LENGTH): cv.uint8_t,
|
||||
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_WHITENING, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +239,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
|
||||
}
|
||||
)
|
||||
.extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()})
|
||||
.extend(CONFIG_MAP)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(spi.spi_device_schema(cs_pin_required=True)),
|
||||
_validate_packet_mode,
|
||||
@@ -229,7 +251,8 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await spi.register_spi_device(var, config)
|
||||
|
||||
for key in CONFIG_MAP:
|
||||
for opt in CONFIG_MAP:
|
||||
key = opt.schema
|
||||
if key in config:
|
||||
cg.add(getattr(var, f"set_{key}")(config[key]))
|
||||
|
||||
|
||||
@@ -98,25 +98,8 @@ CC1101Component::CC1101Component() {
|
||||
this->state_.LENGTH_CONFIG = 2;
|
||||
this->state_.FS_AUTOCAL = 1;
|
||||
|
||||
// Default Settings
|
||||
this->set_frequency(433920000);
|
||||
this->set_if_frequency(153000);
|
||||
this->set_filter_bandwidth(203000);
|
||||
this->set_channel(0);
|
||||
this->set_channel_spacing(200000);
|
||||
this->set_symbol_rate(5000);
|
||||
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
|
||||
this->set_carrier_sense_above_threshold(true);
|
||||
this->set_modulation_type(Modulation::MODULATION_ASK_OOK);
|
||||
this->set_magn_target(MagnTarget::MAGN_TARGET_42DB);
|
||||
this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT);
|
||||
this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3);
|
||||
this->set_lna_priority(false);
|
||||
this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES);
|
||||
|
||||
// CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence)
|
||||
memset(this->pa_table_, 0, sizeof(this->pa_table_));
|
||||
this->set_output_power(10.0f);
|
||||
}
|
||||
|
||||
void CC1101Component::setup() {
|
||||
@@ -157,7 +140,10 @@ void CC1101Component::setup() {
|
||||
this->write_(static_cast<Register>(i));
|
||||
}
|
||||
this->set_output_power(this->output_power_requested_);
|
||||
this->strobe_(Command::RX);
|
||||
if (!this->enter_rx_()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer pin mode setup until after all components have completed setup()
|
||||
// This handles the case where remote_transmitter runs after CC1101 and changes pin mode
|
||||
@@ -180,36 +166,35 @@ void CC1101Component::loop() {
|
||||
ESP_LOGW(TAG, "RX FIFO overflow, flushing");
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
this->enter_rx_();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read packet
|
||||
uint8_t payload_length;
|
||||
uint8_t payload_length, expected_rx;
|
||||
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
|
||||
this->read_(Register::FIFO, &payload_length, 1);
|
||||
expected_rx = payload_length + 1;
|
||||
} else {
|
||||
payload_length = this->state_.PKTLEN;
|
||||
expected_rx = payload_length;
|
||||
}
|
||||
if (payload_length == 0 || payload_length > 64) {
|
||||
ESP_LOGW(TAG, "Invalid payload length: %u", payload_length);
|
||||
if (payload_length == 0 || payload_length > 64 || rx_bytes != expected_rx) {
|
||||
ESP_LOGW(TAG, "Invalid packet: rx_bytes %u, payload_length %u", rx_bytes, payload_length);
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
this->enter_rx_();
|
||||
return;
|
||||
}
|
||||
this->packet_.resize(payload_length);
|
||||
this->read_(Register::FIFO, this->packet_.data(), payload_length);
|
||||
|
||||
// Read status and trigger
|
||||
uint8_t status[2];
|
||||
this->read_(Register::FIFO, status, 2);
|
||||
int8_t rssi_raw = static_cast<int8_t>(status[0]);
|
||||
float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET;
|
||||
bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0;
|
||||
uint8_t lqi = status[1] & STATUS_LQI_MASK;
|
||||
// Read status from registers (more reliable than FIFO status bytes due to timing issues)
|
||||
this->read_(Register::RSSI);
|
||||
this->read_(Register::LQI);
|
||||
float rssi = (this->state_.RSSI * RSSI_STEP) - RSSI_OFFSET;
|
||||
bool crc_ok = (this->state_.LQI & STATUS_CRC_OK_MASK) != 0;
|
||||
uint8_t lqi = this->state_.LQI & STATUS_LQI_MASK;
|
||||
if (this->state_.CRC_EN == 0 || crc_ok) {
|
||||
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
|
||||
}
|
||||
@@ -217,8 +202,7 @@ void CC1101Component::loop() {
|
||||
// Return to rx
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::FRX);
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
this->enter_rx_();
|
||||
}
|
||||
|
||||
void CC1101Component::dump_config() {
|
||||
@@ -249,9 +233,8 @@ void CC1101Component::begin_tx() {
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
}
|
||||
this->strobe_(Command::TX);
|
||||
if (!this->wait_for_state_(State::TX, 50)) {
|
||||
ESP_LOGW(TAG, "Timed out waiting for TX state!");
|
||||
if (!this->enter_tx_()) {
|
||||
ESP_LOGW(TAG, "Failed to enter TX state!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +243,9 @@ void CC1101Component::begin_rx() {
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
}
|
||||
this->strobe_(Command::RX);
|
||||
if (!this->enter_rx_()) {
|
||||
ESP_LOGW(TAG, "Failed to enter RX state!");
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::reset() {
|
||||
@@ -286,11 +271,33 @@ bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CC1101Component::enter_calibrated_(State target_state, Command cmd) {
|
||||
// The PLL must be recalibrated until PLL lock is achieved
|
||||
for (uint8_t retries = PLL_LOCK_RETRIES; retries > 0; retries--) {
|
||||
this->strobe_(cmd);
|
||||
if (!this->wait_for_state_(target_state)) {
|
||||
return false;
|
||||
}
|
||||
this->read_(Register::FSCAL1);
|
||||
if (this->state_.FSCAL1 != FSCAL1_PLL_NOT_LOCKED) {
|
||||
return true;
|
||||
}
|
||||
ESP_LOGW(TAG, "PLL lock failed, retrying calibration");
|
||||
this->enter_idle_();
|
||||
}
|
||||
ESP_LOGE(TAG, "PLL lock failed after retries");
|
||||
return false;
|
||||
}
|
||||
|
||||
void CC1101Component::enter_idle_() {
|
||||
this->strobe_(Command::IDLE);
|
||||
this->wait_for_state_(State::IDLE);
|
||||
}
|
||||
|
||||
bool CC1101Component::enter_rx_() { return this->enter_calibrated_(State::RX, Command::RX); }
|
||||
|
||||
bool CC1101Component::enter_tx_() { return this->enter_calibrated_(State::TX, Command::TX); }
|
||||
|
||||
uint8_t CC1101Component::strobe_(Command cmd) {
|
||||
uint8_t index = static_cast<uint8_t>(cmd);
|
||||
if (cmd < Command::RES || cmd > Command::NOP) {
|
||||
@@ -352,18 +359,26 @@ CC1101Error CC1101Component::transmit_packet(const std::vector<uint8_t> &packet)
|
||||
this->write_(Register::FIFO, static_cast<uint8_t>(packet.size()));
|
||||
}
|
||||
this->write_(Register::FIFO, packet.data(), packet.size());
|
||||
|
||||
// Calibrate PLL
|
||||
if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) {
|
||||
ESP_LOGW(TAG, "PLL lock failed during TX");
|
||||
this->enter_idle_();
|
||||
this->enter_rx_();
|
||||
return CC1101Error::PLL_LOCK;
|
||||
}
|
||||
|
||||
// Transmit packet
|
||||
this->strobe_(Command::TX);
|
||||
if (!this->wait_for_state_(State::IDLE, 1000)) {
|
||||
ESP_LOGW(TAG, "TX timeout");
|
||||
this->enter_idle_();
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
this->enter_rx_();
|
||||
return CC1101Error::TIMEOUT;
|
||||
}
|
||||
|
||||
// Return to rx
|
||||
this->strobe_(Command::RX);
|
||||
this->wait_for_state_(State::RX);
|
||||
this->enter_rx_();
|
||||
return CC1101Error::NONE;
|
||||
}
|
||||
|
||||
@@ -420,7 +435,7 @@ void CC1101Component::set_frequency(float value) {
|
||||
this->write_(Register::FREQ2);
|
||||
this->write_(Register::FREQ1);
|
||||
this->write_(Register::FREQ0);
|
||||
this->strobe_(Command::RX);
|
||||
this->enter_rx_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +462,7 @@ void CC1101Component::set_channel(uint8_t value) {
|
||||
if (this->initialized_) {
|
||||
this->enter_idle_();
|
||||
this->write_(Register::CHANNR);
|
||||
this->strobe_(Command::RX);
|
||||
this->enter_rx_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,7 +531,7 @@ void CC1101Component::set_modulation_type(Modulation value) {
|
||||
this->set_output_power(this->output_power_requested_);
|
||||
this->write_(Register::MDMCFG2);
|
||||
this->write_(Register::FREND0);
|
||||
this->strobe_(Command::RX);
|
||||
this->enter_rx_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,12 +648,15 @@ void CC1101Component::set_packet_mode(bool value) {
|
||||
this->state_.GDO0_CFG = 0x01;
|
||||
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
|
||||
this->state_.FIFO_THR = 15;
|
||||
// Don't append status bytes to FIFO - we read from registers instead
|
||||
this->state_.APPEND_STATUS = 0;
|
||||
} else {
|
||||
// Configure GDO0 for serial data (async serial mode)
|
||||
this->state_.GDO0_CFG = 0x0D;
|
||||
}
|
||||
if (this->initialized_) {
|
||||
this->write_(Register::PKTCTRL0);
|
||||
this->write_(Register::PKTCTRL1);
|
||||
this->write_(Register::IOCFG0);
|
||||
this->write_(Register::FIFOTHR);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace esphome::cc1101 {
|
||||
|
||||
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW };
|
||||
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW, PLL_LOCK };
|
||||
|
||||
class CC1101Component : public Component,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
@@ -102,7 +102,10 @@ class CC1101Component : public Component,
|
||||
|
||||
// State Management
|
||||
bool wait_for_state_(State target_state, uint32_t timeout_ms = 100);
|
||||
bool enter_calibrated_(State target_state, Command cmd);
|
||||
void enter_idle_();
|
||||
bool enter_rx_();
|
||||
bool enter_tx_();
|
||||
};
|
||||
|
||||
// Action Wrappers
|
||||
|
||||
@@ -9,6 +9,9 @@ static constexpr float XTAL_FREQUENCY = 26000000;
|
||||
static constexpr float RSSI_OFFSET = 74.0f;
|
||||
static constexpr float RSSI_STEP = 0.5f;
|
||||
|
||||
static constexpr uint8_t FSCAL1_PLL_NOT_LOCKED = 0x3F;
|
||||
static constexpr uint8_t PLL_LOCK_RETRIES = 3;
|
||||
|
||||
static constexpr uint8_t STATUS_CRC_OK_MASK = 0x80;
|
||||
static constexpr uint8_t STATUS_LQI_MASK = 0x7F;
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ bool MenuItemSelect::select_next() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_next(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -65,6 +66,7 @@ bool MenuItemSelect::select_prev() {
|
||||
|
||||
if (this->select_var_ != nullptr) {
|
||||
this->select_var_->make_call().select_previous(true).perform();
|
||||
this->on_value_();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ from esphome.core import CORE, CoroPriority, TimePeriod, coroutine_with_priority
|
||||
import esphome.final_validate as fv
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
AUTO_LOAD = ["socket"]
|
||||
CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"]
|
||||
DOMAIN = "esp32_ble"
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All(
|
||||
cv.frequency, cv.Range(min=8e6, max=20e6)
|
||||
cv.frequency, cv.float_range(min=8e6, max=20e6)
|
||||
),
|
||||
}
|
||||
),
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace esphome {
|
||||
namespace esp32_camera {
|
||||
|
||||
static const char *const TAG = "esp32_camera";
|
||||
#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
|
||||
static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000;
|
||||
#endif
|
||||
|
||||
/* ---------------- public API (derivated) ---------------- */
|
||||
void ESP32Camera::setup() {
|
||||
@@ -204,7 +207,20 @@ void ESP32Camera::loop() {
|
||||
}
|
||||
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||
|
||||
ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
ESP_LOGV(TAG, "Got Image: len=%u", fb->len);
|
||||
#else
|
||||
// Initialize log time on first frame to ensure accurate interval measurement
|
||||
if (this->frame_count_ == 0) {
|
||||
this->last_log_time_ = now;
|
||||
}
|
||||
this->frame_count_++;
|
||||
if (now - this->last_log_time_ >= FRAME_LOG_INTERVAL_MS) {
|
||||
ESP_LOGD(TAG, "Received %u images in last %us", this->frame_count_, FRAME_LOG_INTERVAL_MS / 1000);
|
||||
this->last_log_time_ = now;
|
||||
this->frame_count_ = 0;
|
||||
}
|
||||
#endif
|
||||
for (auto *listener : this->listeners_) {
|
||||
listener->on_camera_image(this->current_image_);
|
||||
}
|
||||
|
||||
@@ -213,6 +213,10 @@ class ESP32Camera : public camera::Camera {
|
||||
|
||||
uint32_t last_idle_request_{0};
|
||||
uint32_t last_update_{0};
|
||||
#if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE
|
||||
uint32_t last_log_time_{0};
|
||||
uint16_t frame_count_{0};
|
||||
#endif
|
||||
#ifdef USE_I2C
|
||||
i2c::InternalI2CBus *i2c_bus_{nullptr};
|
||||
#endif // USE_I2C
|
||||
|
||||
@@ -16,7 +16,7 @@ def valid_pwm_pin(value):
|
||||
esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm")
|
||||
ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component)
|
||||
SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
|
||||
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
|
||||
@@ -395,12 +395,14 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
|
||||
error:
|
||||
this->write_byte_(static_cast<uint8_t>(error_code));
|
||||
this->cleanup_connection_();
|
||||
|
||||
// Abort backend before cleanup - cleanup_connection_() destroys the backend
|
||||
if (this->backend_ != nullptr && update_started) {
|
||||
this->backend_->abort();
|
||||
}
|
||||
|
||||
this->cleanup_connection_();
|
||||
|
||||
this->status_momentary_error("err", 5000);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
|
||||
@@ -66,11 +66,17 @@ CONF_WAIT_FOR_SENT = "wait_for_sent"
|
||||
MAX_ESPNOW_PACKET_SIZE = 250 # Maximum size of the payload in bytes
|
||||
|
||||
|
||||
def validate_channel(value):
|
||||
if value is None:
|
||||
raise cv.Invalid("channel is required if wifi is not configured")
|
||||
return wifi.validate_channel(value)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESPNowComponent),
|
||||
cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): wifi.validate_channel,
|
||||
cv.OnlyWithout(CONF_CHANNEL, CONF_WIFI): validate_channel,
|
||||
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
|
||||
cv.Optional(CONF_AUTO_ADD_PEER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PEERS): cv.ensure_list(cv.mac_address),
|
||||
|
||||
@@ -93,35 +93,35 @@ CONF_DOUBLE_BUFFER = "double_buffer"
|
||||
CONF_MIN_REFRESH_RATE = "min_refresh_rate"
|
||||
|
||||
# Map to hub75 library enums (in global namespace)
|
||||
ShiftDriver = cg.global_ns.enum("ShiftDriver", is_class=True)
|
||||
Hub75ShiftDriver = cg.global_ns.enum("Hub75ShiftDriver", is_class=True)
|
||||
SHIFT_DRIVERS = {
|
||||
"GENERIC": ShiftDriver.GENERIC,
|
||||
"FM6126A": ShiftDriver.FM6126A,
|
||||
"ICN2038S": ShiftDriver.ICN2038S,
|
||||
"FM6124": ShiftDriver.FM6124,
|
||||
"MBI5124": ShiftDriver.MBI5124,
|
||||
"DP3246": ShiftDriver.DP3246,
|
||||
"GENERIC": Hub75ShiftDriver.GENERIC,
|
||||
"FM6126A": Hub75ShiftDriver.FM6126A,
|
||||
"ICN2038S": Hub75ShiftDriver.ICN2038S,
|
||||
"FM6124": Hub75ShiftDriver.FM6124,
|
||||
"MBI5124": Hub75ShiftDriver.MBI5124,
|
||||
"DP3246": Hub75ShiftDriver.DP3246,
|
||||
}
|
||||
|
||||
PanelLayout = cg.global_ns.enum("PanelLayout", is_class=True)
|
||||
Hub75PanelLayout = cg.global_ns.enum("Hub75PanelLayout", is_class=True)
|
||||
PANEL_LAYOUTS = {
|
||||
"HORIZONTAL": PanelLayout.HORIZONTAL,
|
||||
"TOP_LEFT_DOWN": PanelLayout.TOP_LEFT_DOWN,
|
||||
"TOP_RIGHT_DOWN": PanelLayout.TOP_RIGHT_DOWN,
|
||||
"BOTTOM_LEFT_UP": PanelLayout.BOTTOM_LEFT_UP,
|
||||
"BOTTOM_RIGHT_UP": PanelLayout.BOTTOM_RIGHT_UP,
|
||||
"TOP_LEFT_DOWN_ZIGZAG": PanelLayout.TOP_LEFT_DOWN_ZIGZAG,
|
||||
"TOP_RIGHT_DOWN_ZIGZAG": PanelLayout.TOP_RIGHT_DOWN_ZIGZAG,
|
||||
"BOTTOM_LEFT_UP_ZIGZAG": PanelLayout.BOTTOM_LEFT_UP_ZIGZAG,
|
||||
"BOTTOM_RIGHT_UP_ZIGZAG": PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG,
|
||||
"HORIZONTAL": Hub75PanelLayout.HORIZONTAL,
|
||||
"TOP_LEFT_DOWN": Hub75PanelLayout.TOP_LEFT_DOWN,
|
||||
"TOP_RIGHT_DOWN": Hub75PanelLayout.TOP_RIGHT_DOWN,
|
||||
"BOTTOM_LEFT_UP": Hub75PanelLayout.BOTTOM_LEFT_UP,
|
||||
"BOTTOM_RIGHT_UP": Hub75PanelLayout.BOTTOM_RIGHT_UP,
|
||||
"TOP_LEFT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_LEFT_DOWN_ZIGZAG,
|
||||
"TOP_RIGHT_DOWN_ZIGZAG": Hub75PanelLayout.TOP_RIGHT_DOWN_ZIGZAG,
|
||||
"BOTTOM_LEFT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_LEFT_UP_ZIGZAG,
|
||||
"BOTTOM_RIGHT_UP_ZIGZAG": Hub75PanelLayout.BOTTOM_RIGHT_UP_ZIGZAG,
|
||||
}
|
||||
|
||||
ScanPattern = cg.global_ns.enum("ScanPattern", is_class=True)
|
||||
Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True)
|
||||
SCAN_PATTERNS = {
|
||||
"STANDARD_TWO_SCAN": ScanPattern.STANDARD_TWO_SCAN,
|
||||
"FOUR_SCAN_16PX_HIGH": ScanPattern.FOUR_SCAN_16PX_HIGH,
|
||||
"FOUR_SCAN_32PX_HIGH": ScanPattern.FOUR_SCAN_32PX_HIGH,
|
||||
"FOUR_SCAN_64PX_HIGH": ScanPattern.FOUR_SCAN_64PX_HIGH,
|
||||
"STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN,
|
||||
"FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH,
|
||||
"FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH,
|
||||
"FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_64PX_HIGH,
|
||||
}
|
||||
|
||||
Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True)
|
||||
@@ -528,7 +528,7 @@ def _build_config_struct(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
add_idf_component(
|
||||
name="esphome/esp-hub75",
|
||||
ref="0.1.6",
|
||||
ref="0.1.7",
|
||||
)
|
||||
|
||||
# Set compile-time configuration via defines
|
||||
|
||||
@@ -111,6 +111,9 @@ void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) {
|
||||
if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return;
|
||||
|
||||
driver_->set_pixel(x, y, color.r, color.g, color.b);
|
||||
App.feed_wdt();
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
nrf52="100kHz",
|
||||
): cv.All(
|
||||
cv.frequency,
|
||||
cv.Range(min=0, min_included=False),
|
||||
cv.float_range(min=0, min_included=False),
|
||||
),
|
||||
cv.Optional(CONF_TIMEOUT): cv.All(
|
||||
cv.only_with_framework(["arduino", "esp-idf"]),
|
||||
|
||||
@@ -45,7 +45,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(LEDCOutput),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, min_included=False)
|
||||
),
|
||||
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_PHASE_ANGLE): cv.All(
|
||||
cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0)
|
||||
|
||||
@@ -14,7 +14,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
|
||||
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, min_included=False)
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ from esphome.const import (
|
||||
CONF_REPEAT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ICON_BRIGHTNESS_5,
|
||||
ICON_BRIGHTNESS_6,
|
||||
@@ -169,7 +168,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
@@ -179,7 +177,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_7,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
@@ -189,7 +186,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_PROXIMITY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
@@ -198,7 +194,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
icon=ICON_GAIN,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
|
||||
@@ -5,7 +5,7 @@ Constants already defined in esphome.const are not duplicated here and must be i
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.const import CONF_ITEMS
|
||||
@@ -96,13 +96,9 @@ class LValidator:
|
||||
return None
|
||||
if isinstance(value, Lambda):
|
||||
# Local import to avoid circular import
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
return cg.RawExpression(
|
||||
call_lambda(
|
||||
await cg.process_lambda(value, args, return_type=self.rtype)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import image
|
||||
@@ -404,14 +404,9 @@ class TextValidator(LValidator):
|
||||
self, value: Any, args: list[tuple[SafeExpType, str]] | None = None
|
||||
) -> Expression:
|
||||
# Local import to avoid circular import at module level
|
||||
from .lvcode import get_lambda_context_args
|
||||
|
||||
from .lvcode import CodeContext, LambdaContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext does not have get_automation_parameters
|
||||
# so we need to assert the type here
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
args = args or CodeContext.code_context.get_automation_parameters()
|
||||
args = args or get_lambda_context_args()
|
||||
|
||||
if isinstance(value, dict):
|
||||
if format_str := value.get(CONF_FORMAT):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from esphome import codegen as cg
|
||||
from esphome.config import Config
|
||||
@@ -200,6 +201,21 @@ class LvContext(LambdaContext):
|
||||
return self.add(*args)
|
||||
|
||||
|
||||
def get_lambda_context_args() -> list[tuple[SafeExpType, str]]:
|
||||
"""Get automation parameters from the current lambda context if available.
|
||||
|
||||
When called from outside LVGL's context (e.g., from interval),
|
||||
CodeContext.code_context will be None, so return empty args.
|
||||
"""
|
||||
if CodeContext.code_context is None:
|
||||
return []
|
||||
if TYPE_CHECKING:
|
||||
# CodeContext base class doesn't define get_automation_parameters(),
|
||||
# but LambdaContext and LvContext (the concrete implementations) do.
|
||||
assert isinstance(CodeContext.code_context, LambdaContext)
|
||||
return CodeContext.code_context.get_automation_parameters()
|
||||
|
||||
|
||||
class LocalVariable(MockObj):
|
||||
"""
|
||||
Create a local variable and enclose the code using it within a block.
|
||||
|
||||
@@ -85,11 +85,11 @@ class ArcType(NumberType):
|
||||
lv.arc_set_range(w.obj, min_value, max_value)
|
||||
|
||||
await w.set_property(
|
||||
CONF_START_ANGLE,
|
||||
"bg_start_angle",
|
||||
await lv_angle_degrees.process(config.get(CONF_START_ANGLE)),
|
||||
)
|
||||
await w.set_property(
|
||||
CONF_END_ANGLE, await lv_angle_degrees.process(config.get(CONF_END_ANGLE))
|
||||
"bg_end_angle", await lv_angle_degrees.process(config.get(CONF_END_ANGLE))
|
||||
)
|
||||
await w.set_property(
|
||||
CONF_ROTATION, await lv_angle_degrees.process(config.get(CONF_ROTATION))
|
||||
|
||||
@@ -269,7 +269,7 @@ void PacketTransport::flush_() {
|
||||
|
||||
void PacketTransport::add_binary_data_(uint8_t key, const char *id, bool data) {
|
||||
auto len = 1 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > this->get_max_packet_size()) {
|
||||
if (round4(this->header_.size()) + round4(this->data_.size() + len) > this->get_max_packet_size()) {
|
||||
this->flush_();
|
||||
this->init_data_();
|
||||
}
|
||||
@@ -284,7 +284,7 @@ void PacketTransport::add_data_(uint8_t key, const char *id, float data) {
|
||||
|
||||
void PacketTransport::add_data_(uint8_t key, const char *id, uint32_t data) {
|
||||
auto len = 4 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > this->get_max_packet_size()) {
|
||||
if (round4(this->header_.size()) + round4(this->data_.size() + len) > this->get_max_packet_size()) {
|
||||
this->flush_();
|
||||
this->init_data_();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PCA9685Output),
|
||||
cv.Optional(CONF_FREQUENCY): cv.All(
|
||||
cv.frequency, cv.Range(min=23.84, max=1525.88)
|
||||
cv.frequency, cv.float_range(min=23.84, max=1525.88)
|
||||
),
|
||||
cv.Optional(CONF_EXTERNAL_CLOCK_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PHASE_BALANCER, default="linear"): cv.enum(
|
||||
|
||||
@@ -40,13 +40,10 @@ void RemoteTransmitterComponent::await_target_time_() {
|
||||
if (this->target_time_ == 0) {
|
||||
this->target_time_ = current_time;
|
||||
} else if ((int32_t) (this->target_time_ - current_time) > 0) {
|
||||
#if defined(USE_LIBRETINY) || defined(USE_RP2040)
|
||||
// busy loop is required for libretiny and rp2040 as interrupts are disabled
|
||||
// busy loop is required as interrupts are disabled and delayMicroseconds()
|
||||
// may not work correctly in interrupt-disabled contexts on all platforms
|
||||
while ((int32_t) (this->target_time_ - micros()) > 0)
|
||||
;
|
||||
#else
|
||||
delayMicroseconds(this->target_time_ - current_time);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ DEPENDENCIES = ["rp2040"]
|
||||
rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm")
|
||||
RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component)
|
||||
SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action)
|
||||
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
|
||||
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
|
||||
@@ -64,15 +64,21 @@ void MR24HPC1Component::dump_config() {
|
||||
void MR24HPC1Component::setup() {
|
||||
this->check_uart_settings(115200);
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0); // Zero out the custom mode
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Not in custom mode");
|
||||
}
|
||||
#endif
|
||||
this->set_custom_end_mode();
|
||||
this->poll_time_base_func_check_ = true;
|
||||
this->check_dev_inf_sign_ = true;
|
||||
@@ -353,6 +359,7 @@ void MR24HPC1Component::r24_split_data_frame_(uint8_t value) {
|
||||
|
||||
// Parses data frames related to product information
|
||||
void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
uint16_t product_len = encode_uint16(data[FRAME_COMMAND_WORD_INDEX + 1], data[FRAME_COMMAND_WORD_INDEX + 2]);
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == COMMAND_PRODUCT_MODE) {
|
||||
if ((this->product_model_text_sensor_ != nullptr) && (product_len < PRODUCT_BUF_MAX_SIZE)) {
|
||||
@@ -388,109 +395,153 @@ void MR24HPC1Component::r24_frame_parse_product_information_(uint8_t *data) {
|
||||
ESP_LOGD(TAG, "Reply: get firmwareVersion error!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Parsing the underlying open parameters
|
||||
void MR24HPC1Component::r24_frame_parse_open_underlying_information_(uint8_t *data) {
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == 0x00) {
|
||||
if (this->underlying_open_function_switch_ != nullptr) {
|
||||
this->underlying_open_function_switch_->publish_state(
|
||||
data[FRAME_DATA_INDEX]); // Underlying Open Parameter Switch Status Updates
|
||||
}
|
||||
if (data[FRAME_DATA_INDEX]) {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON;
|
||||
} else {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF;
|
||||
}
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) {
|
||||
if (this->custom_spatial_static_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
if (this->custom_presence_of_detection_sensor_ != nullptr) {
|
||||
this->custom_presence_of_detection_sensor_->publish_state(data[FRAME_DATA_INDEX + 1] * 0.5f);
|
||||
}
|
||||
if (this->custom_spatial_motion_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX + 2]);
|
||||
}
|
||||
if (this->custom_motion_distance_sensor_ != nullptr) {
|
||||
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX + 3] * 0.5f);
|
||||
}
|
||||
if (this->custom_motion_speed_sensor_ != nullptr) {
|
||||
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX + 4] - 10) * 0.5f);
|
||||
}
|
||||
} else if ((data[FRAME_COMMAND_WORD_INDEX] == 0x06) || (data[FRAME_COMMAND_WORD_INDEX] == 0x86)) {
|
||||
// none:0x00 close_to:0x01 far_away:0x02
|
||||
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
|
||||
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
} else if ((this->movement_signs_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x07) || (data[FRAME_COMMAND_WORD_INDEX] == 0x87))) {
|
||||
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->existence_threshold_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) {
|
||||
this->existence_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->motion_threshold_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x09) || (data[FRAME_COMMAND_WORD_INDEX] == 0x89))) {
|
||||
this->motion_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->existence_boundary_select_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0a) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8a))) {
|
||||
if (this->existence_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
|
||||
this->existence_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
|
||||
}
|
||||
} else if ((this->motion_boundary_select_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0b) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8b))) {
|
||||
if (this->motion_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
|
||||
this->motion_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
|
||||
}
|
||||
} else if ((this->motion_trigger_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0c) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8c))) {
|
||||
uint32_t motion_trigger_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
this->motion_trigger_number_->publish_state(motion_trigger_time);
|
||||
} else if ((this->motion_to_rest_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0d) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8d))) {
|
||||
uint32_t move_to_rest_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
this->motion_to_rest_number_->publish_state(move_to_rest_time);
|
||||
} else if ((this->custom_unman_time_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0e) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8e))) {
|
||||
uint32_t enter_unmanned_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
float custom_unmanned_time = enter_unmanned_time / 1000.0;
|
||||
this->custom_unman_time_number_->publish_state(custom_unmanned_time);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x80) {
|
||||
if (data[FRAME_DATA_INDEX]) {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWITCH_ON;
|
||||
} else {
|
||||
this->s_output_info_switch_flag_ = OUTPUT_SWTICH_OFF;
|
||||
}
|
||||
if (this->underlying_open_function_switch_ != nullptr) {
|
||||
this->underlying_open_function_switch_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->custom_spatial_static_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x81)) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->custom_spatial_motion_value_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x82)) {
|
||||
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->custom_presence_of_detection_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x83)) {
|
||||
this->custom_presence_of_detection_sensor_->publish_state(
|
||||
S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]);
|
||||
} else if ((this->custom_motion_distance_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x84)) {
|
||||
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f);
|
||||
} else if ((this->custom_motion_speed_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x85)) {
|
||||
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f);
|
||||
switch (data[FRAME_COMMAND_WORD_INDEX]) {
|
||||
case 0x00:
|
||||
case 0x80:
|
||||
#ifdef USE_SWITCH
|
||||
if (this->underlying_open_function_switch_ != nullptr) {
|
||||
this->underlying_open_function_switch_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
#endif
|
||||
this->s_output_info_switch_flag_ = data[FRAME_DATA_INDEX] ? OUTPUT_SWITCH_ON : OUTPUT_SWTICH_OFF;
|
||||
break;
|
||||
#ifdef USE_SENSOR
|
||||
case 0x01:
|
||||
if (this->custom_spatial_static_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
if (this->custom_presence_of_detection_sensor_ != nullptr) {
|
||||
this->custom_presence_of_detection_sensor_->publish_state(data[FRAME_DATA_INDEX + 1] * 0.5f);
|
||||
}
|
||||
if (this->custom_spatial_motion_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX + 2]);
|
||||
}
|
||||
if (this->custom_motion_distance_sensor_ != nullptr) {
|
||||
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX + 3] * 0.5f);
|
||||
}
|
||||
if (this->custom_motion_speed_sensor_ != nullptr) {
|
||||
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX + 4] - 10) * 0.5f);
|
||||
}
|
||||
break;
|
||||
case 0x07:
|
||||
case 0x87:
|
||||
if (this->movement_signs_sensor_ != nullptr) {
|
||||
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
case 0x81:
|
||||
if (this->custom_spatial_static_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
case 0x82:
|
||||
if (this->custom_spatial_motion_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_motion_value_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
case 0x83:
|
||||
if (this->custom_presence_of_detection_sensor_ != nullptr) {
|
||||
this->custom_presence_of_detection_sensor_->publish_state(
|
||||
S_PRESENCE_OF_DETECTION_RANGE_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
break;
|
||||
case 0x84:
|
||||
if (this->custom_motion_distance_sensor_ != nullptr) {
|
||||
this->custom_motion_distance_sensor_->publish_state(data[FRAME_DATA_INDEX] * 0.5f);
|
||||
}
|
||||
break;
|
||||
case 0x85:
|
||||
if (this->custom_motion_speed_sensor_ != nullptr) {
|
||||
this->custom_motion_speed_sensor_->publish_state((data[FRAME_DATA_INDEX] - 10) * 0.5f);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
case 0x06:
|
||||
case 0x86:
|
||||
// none:0x00 close_to:0x01 far_away:0x02
|
||||
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
|
||||
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
case 0x08:
|
||||
case 0x88:
|
||||
if (this->existence_threshold_number_ != nullptr) {
|
||||
this->existence_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
case 0x09:
|
||||
case 0x89:
|
||||
if (this->motion_threshold_number_ != nullptr) {
|
||||
this->motion_threshold_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
case 0x0c:
|
||||
case 0x8c:
|
||||
if (this->motion_trigger_number_ != nullptr) {
|
||||
uint32_t motion_trigger_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
this->motion_trigger_number_->publish_state(motion_trigger_time);
|
||||
}
|
||||
break;
|
||||
case 0x0d:
|
||||
case 0x8d:
|
||||
if (this->motion_to_rest_number_ != nullptr) {
|
||||
uint32_t move_to_rest_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
this->motion_to_rest_number_->publish_state(move_to_rest_time);
|
||||
}
|
||||
break;
|
||||
case 0x0e:
|
||||
case 0x8e:
|
||||
if (this->custom_unman_time_number_ != nullptr) {
|
||||
uint32_t enter_unmanned_time = encode_uint32(data[FRAME_DATA_INDEX], data[FRAME_DATA_INDEX + 1],
|
||||
data[FRAME_DATA_INDEX + 2], data[FRAME_DATA_INDEX + 3]);
|
||||
this->custom_unman_time_number_->publish_state(enter_unmanned_time / 1000.0f);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
case 0x0a:
|
||||
case 0x8a:
|
||||
if (this->existence_boundary_select_ != nullptr) {
|
||||
if (this->existence_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
|
||||
this->existence_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x0b:
|
||||
case 0x8b:
|
||||
if (this->motion_boundary_select_ != nullptr) {
|
||||
if (this->motion_boundary_select_->has_index(data[FRAME_DATA_INDEX] - 1)) {
|
||||
this->motion_boundary_select_->publish_state(data[FRAME_DATA_INDEX] - 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
|
||||
switch (data[FRAME_CONTROL_WORD_INDEX]) {
|
||||
case 0x01: {
|
||||
if ((this->heartbeat_state_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x01)) {
|
||||
this->heartbeat_state_text_sensor_->publish_state("Equipment Normal");
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) {
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == 0x02) {
|
||||
ESP_LOGD(TAG, "Reply: query restart packet");
|
||||
} else if (this->heartbeat_state_text_sensor_ != nullptr) {
|
||||
this->heartbeat_state_text_sensor_->publish_state("Equipment Abnormal");
|
||||
break;
|
||||
}
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->heartbeat_state_text_sensor_ != nullptr) {
|
||||
this->heartbeat_state_text_sensor_->publish_state(
|
||||
data[FRAME_COMMAND_WORD_INDEX] == 0x01 ? "Equipment Normal" : "Equipment Abnormal");
|
||||
}
|
||||
#endif
|
||||
} break;
|
||||
case 0x02: {
|
||||
this->r24_frame_parse_product_information_(data);
|
||||
@@ -511,86 +562,123 @@ void MR24HPC1Component::r24_parse_data_frame_(uint8_t *data, uint8_t len) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::r24_frame_parse_work_status_(uint8_t *data) {
|
||||
if (data[FRAME_COMMAND_WORD_INDEX] == 0x01) {
|
||||
ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x07) {
|
||||
if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) {
|
||||
this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->sensitivity_number_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x08) || (data[FRAME_COMMAND_WORD_INDEX] == 0x88))) {
|
||||
// 1-3
|
||||
this->sensitivity_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x09) {
|
||||
// 1-4
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Setup in progress");
|
||||
}
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x81) {
|
||||
ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]);
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x87) {
|
||||
if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) {
|
||||
this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->custom_mode_end_text_sensor_ != nullptr) && (data[FRAME_COMMAND_WORD_INDEX] == 0x0A)) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Set Success!");
|
||||
} else if (data[FRAME_COMMAND_WORD_INDEX] == 0x89) {
|
||||
if (data[FRAME_DATA_INDEX] == 0) {
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Not in custom mode");
|
||||
switch (data[FRAME_COMMAND_WORD_INDEX]) {
|
||||
case 0x01:
|
||||
case 0x81:
|
||||
ESP_LOGD(TAG, "Reply: get radar init status 0x%02X", data[FRAME_DATA_INDEX]);
|
||||
break;
|
||||
case 0x09:
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Setup in progress");
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case 0x89:
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else {
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
#endif
|
||||
if (data[FRAME_DATA_INDEX] == 0) {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Not in custom mode");
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
|
||||
break;
|
||||
#ifdef USE_SELECT
|
||||
case 0x07:
|
||||
case 0x87:
|
||||
if ((this->scene_mode_select_ != nullptr) && (this->scene_mode_select_->has_index(data[FRAME_DATA_INDEX]))) {
|
||||
this->scene_mode_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Select has index offset %d Error", data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
case 0x08:
|
||||
case 0x88:
|
||||
if (this->sensitivity_number_ != nullptr) {
|
||||
this->sensitivity_number_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
case 0x0A:
|
||||
if (this->custom_mode_end_text_sensor_ != nullptr) {
|
||||
this->custom_mode_end_text_sensor_->publish_state("Set Success!");
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MR24HPC1Component::r24_frame_parse_human_information_(uint8_t *data) {
|
||||
if ((this->has_target_binary_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x01) || (data[FRAME_COMMAND_WORD_INDEX] == 0x81))) {
|
||||
this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]);
|
||||
} else if ((this->motion_status_text_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x02) || (data[FRAME_COMMAND_WORD_INDEX] == 0x82))) {
|
||||
if (data[FRAME_DATA_INDEX] < 3) {
|
||||
this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
} else if ((this->movement_signs_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x03) || (data[FRAME_COMMAND_WORD_INDEX] == 0x83))) {
|
||||
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
} else if ((this->unman_time_select_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0A) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8A))) {
|
||||
// none:0x00 1s:0x01 30s:0x02 1min:0x03 2min:0x04 5min:0x05 10min:0x06 30min:0x07 1hour:0x08
|
||||
if (data[FRAME_DATA_INDEX] < 9) {
|
||||
this->unman_time_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
} else if ((this->keep_away_text_sensor_ != nullptr) &&
|
||||
((data[FRAME_COMMAND_WORD_INDEX] == 0x0B) || (data[FRAME_COMMAND_WORD_INDEX] == 0x8B))) {
|
||||
// none:0x00 close_to:0x01 far_away:0x02
|
||||
if (data[FRAME_DATA_INDEX] < 3) {
|
||||
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
|
||||
switch (data[FRAME_COMMAND_WORD_INDEX]) {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
case 0x01:
|
||||
case 0x81:
|
||||
if (this->has_target_binary_sensor_ != nullptr) {
|
||||
this->has_target_binary_sensor_->publish_state(S_SOMEONE_EXISTS_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
case 0x03:
|
||||
case 0x83:
|
||||
if (this->movement_signs_sensor_ != nullptr) {
|
||||
this->movement_signs_sensor_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
case 0x02:
|
||||
case 0x82:
|
||||
if ((this->motion_status_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
|
||||
this->motion_status_text_sensor_->publish_state(S_MOTION_STATUS_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
break;
|
||||
case 0x0B:
|
||||
case 0x8B:
|
||||
// none:0x00 close_to:0x01 far_away:0x02
|
||||
if ((this->keep_away_text_sensor_ != nullptr) && (data[FRAME_DATA_INDEX] < 3)) {
|
||||
this->keep_away_text_sensor_->publish_state(S_KEEP_AWAY_STR[data[FRAME_DATA_INDEX]]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
case 0x0A:
|
||||
case 0x8A:
|
||||
// none:0x00 1s:0x01 30s:0x02 1min:0x03 2min:0x04 5min:0x05 10min:0x06 30min:0x07 1hour:0x08
|
||||
if ((this->unman_time_select_ != nullptr) && (data[FRAME_DATA_INDEX] < 9)) {
|
||||
this->unman_time_select_->publish_state(data[FRAME_DATA_INDEX]);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
ESP_LOGD(TAG, "[%s] No found COMMAND_WORD(%02X) in Frame", __FUNCTION__, data[FRAME_COMMAND_WORD_INDEX]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,12 +783,15 @@ void MR24HPC1Component::set_underlying_open_function(bool enable) {
|
||||
} else {
|
||||
this->send_query_(UNDERLYING_SWITCH_OFF, sizeof(UNDERLYING_SWITCH_OFF));
|
||||
}
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->keep_away_text_sensor_ != nullptr) {
|
||||
this->keep_away_text_sensor_->publish_state("");
|
||||
}
|
||||
if (this->motion_status_text_sensor_ != nullptr) {
|
||||
this->motion_status_text_sensor_->publish_state("");
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_spatial_static_value_sensor_ != nullptr) {
|
||||
this->custom_spatial_static_value_sensor_->publish_state(NAN);
|
||||
}
|
||||
@@ -716,6 +807,7 @@ void MR24HPC1Component::set_underlying_open_function(bool enable) {
|
||||
if (this->custom_motion_speed_sensor_ != nullptr) {
|
||||
this->custom_motion_speed_sensor_->publish_state(NAN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_scene_mode(uint8_t value) {
|
||||
@@ -723,12 +815,16 @@ void MR24HPC1Component::set_scene_mode(uint8_t value) {
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x07, 0x00, 0x01, value, 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
this->send_query_(send_data, send_data_len);
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (this->custom_mode_num_sensor_ != nullptr) {
|
||||
this->custom_mode_num_sensor_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
this->get_scene_mode();
|
||||
this->get_sensitivity();
|
||||
this->get_custom_mode();
|
||||
@@ -768,9 +864,11 @@ void MR24HPC1Component::set_unman_time(uint8_t value) {
|
||||
void MR24HPC1Component::set_custom_mode(uint8_t mode) {
|
||||
if (mode == 0) {
|
||||
this->set_custom_end_mode(); // Equivalent to end setting
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0);
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
uint8_t send_data_len = 10;
|
||||
@@ -793,9 +891,11 @@ void MR24HPC1Component::set_custom_end_mode() {
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x05, 0x0a, 0x00, 0x01, 0x0F, 0xCB, 0x54, 0x43};
|
||||
this->send_query_(send_data, send_data_len);
|
||||
#ifdef USE_NUMBER
|
||||
if (this->custom_mode_number_ != nullptr) {
|
||||
this->custom_mode_number_->publish_state(0); // Clear setpoints
|
||||
}
|
||||
#endif
|
||||
this->get_existence_boundary();
|
||||
this->get_motion_boundary();
|
||||
this->get_existence_threshold();
|
||||
@@ -809,8 +909,10 @@ void MR24HPC1Component::set_custom_end_mode() {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_existence_boundary(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0A, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -819,8 +921,10 @@ void MR24HPC1Component::set_existence_boundary(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_boundary(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x0B, 0x00, 0x01, (uint8_t) (value + 1), 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -829,8 +933,10 @@ void MR24HPC1Component::set_motion_boundary(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_existence_threshold(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x08, 0x00, 0x01, value, 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -839,8 +945,10 @@ void MR24HPC1Component::set_existence_threshold(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_threshold(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 10;
|
||||
uint8_t send_data[10] = {0x53, 0x59, 0x08, 0x09, 0x00, 0x01, value, 0x00, 0x54, 0x43};
|
||||
send_data[7] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -849,8 +957,10 @@ void MR24HPC1Component::set_motion_threshold(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_trigger_time(uint8_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t send_data_len = 13;
|
||||
uint8_t send_data[13] = {0x53, 0x59, 0x08, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, value, 0x00, 0x54, 0x43};
|
||||
send_data[10] = get_frame_crc_sum(send_data, send_data_len);
|
||||
@@ -859,8 +969,10 @@ void MR24HPC1Component::set_motion_trigger_time(uint8_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint8_t h8_num = (value >> 8) & 0xff;
|
||||
uint8_t l8_num = value & 0xff;
|
||||
uint8_t send_data_len = 13;
|
||||
@@ -871,8 +983,10 @@ void MR24HPC1Component::set_motion_to_rest_time(uint16_t value) {
|
||||
}
|
||||
|
||||
void MR24HPC1Component::set_custom_unman_time(uint16_t value) {
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->custom_mode_num_sensor_ != nullptr) && (this->custom_mode_num_sensor_->state == 0))
|
||||
return; // You'll have to check that you're in custom mode to set it up
|
||||
#endif
|
||||
uint32_t value_ms = value * 1000;
|
||||
uint8_t h24_num = (value_ms >> 24) & 0xff;
|
||||
uint8_t h16_num = (value_ms >> 16) & 0xff;
|
||||
|
||||
@@ -70,7 +70,7 @@ void SN74HC595GPIOComponent::write_gpio() {
|
||||
void SN74HC595SPIComponent::write_gpio() {
|
||||
for (uint8_t &output_byte : std::ranges::reverse_view(this->output_bytes_)) {
|
||||
this->enable();
|
||||
this->transfer_byte(output_byte);
|
||||
this->write_byte(output_byte);
|
||||
this->disable();
|
||||
}
|
||||
SN74HC595Component::write_gpio();
|
||||
|
||||
@@ -47,6 +47,8 @@ def require_wake_loop_threadsafe() -> None:
|
||||
This enables the shared UDP loopback socket mechanism (~208 bytes RAM).
|
||||
The socket is shared across all components that use this feature.
|
||||
|
||||
This call is a no-op if networking is not enabled in the configuration.
|
||||
|
||||
IMPORTANT: This is for background thread context only, NOT ISR context.
|
||||
Socket operations are not safe to call from ISR handlers.
|
||||
|
||||
@@ -56,8 +58,11 @@ def require_wake_loop_threadsafe() -> None:
|
||||
async def to_code(config):
|
||||
socket.require_wake_loop_threadsafe()
|
||||
"""
|
||||
|
||||
# Only set up once (idempotent - multiple components can call this)
|
||||
if not CORE.data.get(KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False):
|
||||
if CORE.has_networking and not CORE.data.get(
|
||||
KEY_WAKE_LOOP_THREADSAFE_REQUIRED, False
|
||||
):
|
||||
CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True
|
||||
cg.add_define("USE_WAKE_LOOP_THREADSAFE")
|
||||
# Consume 1 socket for the shared wake notification socket
|
||||
|
||||
@@ -199,9 +199,13 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_CRC_INITIAL, default=0x1D0F): cv.All(
|
||||
cv.hex_int, cv.Range(min=0, max=0xFFFF)
|
||||
),
|
||||
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
|
||||
cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, max=100000)
|
||||
),
|
||||
cv.Required(CONF_DIO1_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
|
||||
cv.Required(CONF_FREQUENCY): cv.All(
|
||||
cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6)
|
||||
),
|
||||
cv.Required(CONF_HW_VERSION): cv.one_of(
|
||||
"sx1261", "sx1262", "sx1268", "llcc68", lower=True
|
||||
),
|
||||
|
||||
@@ -196,9 +196,13 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_BITSYNC): cv.boolean,
|
||||
cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE),
|
||||
cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000),
|
||||
cv.Optional(CONF_DEVIATION, default="5kHz"): cv.All(
|
||||
cv.frequency, cv.float_range(min=0, max=100000)
|
||||
),
|
||||
cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000),
|
||||
cv.Required(CONF_FREQUENCY): cv.All(
|
||||
cv.frequency, cv.float_range(min=137.0e6, max=1020.0e6)
|
||||
),
|
||||
cv.Required(CONF_MODULATION): cv.enum(MOD),
|
||||
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN),
|
||||
|
||||
@@ -34,7 +34,15 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t
|
||||
severity = LOG_LEVEL_TO_SYSLOG_SEVERITY[level];
|
||||
}
|
||||
int pri = this->facility_ * 8 + severity;
|
||||
auto timestamp = this->time_->now().strftime("%b %e %H:%M:%S");
|
||||
auto now = this->time_->now();
|
||||
std::string timestamp;
|
||||
if (now.is_valid()) {
|
||||
timestamp = now.strftime("%b %e %H:%M:%S");
|
||||
} else {
|
||||
// RFC 5424: A syslog application MUST use the NILVALUE as TIMESTAMP if the syslog application is incapable of
|
||||
// obtaining system time.
|
||||
timestamp = "-";
|
||||
}
|
||||
size_t len = message_len;
|
||||
// remove color formatting
|
||||
if (this->strip_ && message[0] == 0x1B && len > 11) {
|
||||
|
||||
@@ -528,6 +528,16 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(global_wifi_component->wifi_ssid(), global_wifi_component->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = global_wifi_component->get_selected_sta_();
|
||||
config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : global_wifi_component->ip_state_listeners_) {
|
||||
listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(),
|
||||
global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -483,6 +483,12 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) {
|
||||
s_sta_connected = false;
|
||||
s_sta_connect_error = false;
|
||||
s_sta_connect_not_found = false;
|
||||
// Reset IP address flags - ensures we don't report connected before DHCP completes
|
||||
// (IP_EVENT_STA_LOST_IP doesn't always fire on disconnect)
|
||||
this->got_ipv4_address_ = false;
|
||||
#if USE_NETWORK_IPV6
|
||||
this->num_ipv6_addresses_ = 0;
|
||||
#endif
|
||||
|
||||
err = esp_wifi_connect();
|
||||
if (err != ESP_OK) {
|
||||
@@ -739,6 +745,14 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
|
||||
@@ -305,6 +305,14 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -259,6 +259,15 @@ void WiFiComponent::wifi_loop_() {
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(this->wifi_ssid(), this->wifi_bssid());
|
||||
}
|
||||
// For static IP configurations, notify IP listeners immediately as the IP is already configured
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
s_sta_had_ip = true;
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
} else if (!is_connected && s_sta_was_connected) {
|
||||
// Just disconnected
|
||||
|
||||
@@ -71,17 +71,20 @@ void WTS01Sensor::process_packet_() {
|
||||
}
|
||||
|
||||
// Extract temperature value
|
||||
int8_t temp = this->buffer_[6];
|
||||
int32_t sign = 1;
|
||||
const uint8_t raw = this->buffer_[6];
|
||||
|
||||
// Handle negative temperatures
|
||||
if (temp < 0) {
|
||||
sign = -1;
|
||||
// WTS01 encodes sign in bit 7, magnitude in bits 0-6
|
||||
const bool negative = (raw & 0x80) != 0;
|
||||
const uint8_t magnitude = raw & 0x7F;
|
||||
|
||||
const float decimal = static_cast<float>(this->buffer_[7]) / 100.0f;
|
||||
|
||||
float temperature = static_cast<float>(magnitude) + decimal;
|
||||
|
||||
if (negative) {
|
||||
temperature = -temperature;
|
||||
}
|
||||
|
||||
// Calculate temperature (temp + decimal/100)
|
||||
float temperature = static_cast<float>(temp) + (sign * static_cast<float>(this->buffer_[7]) / 100.0f);
|
||||
|
||||
ESP_LOGV(TAG, "Received new temperature: %.2f°C", temperature);
|
||||
|
||||
this->publish_state(temperature);
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.12.1"
|
||||
__version__ = "2025.12.6"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -703,6 +703,25 @@ class EsphomeCore:
|
||||
def config_filename(self) -> str:
|
||||
return self.config_path.name
|
||||
|
||||
def has_at_least_one_component(self, *components: str) -> bool:
|
||||
"""
|
||||
Are any of the given components configured?
|
||||
:param components: component names
|
||||
:return: true if so
|
||||
"""
|
||||
if self.config is None:
|
||||
raise ValueError("Config has not been loaded yet")
|
||||
|
||||
return any(component in self.config for component in components)
|
||||
|
||||
@property
|
||||
def has_networking(self) -> bool:
|
||||
"""
|
||||
Is a network component configured?
|
||||
:return: true if so
|
||||
"""
|
||||
return self.has_at_least_one_component("wifi", "ethernet", "openthread")
|
||||
|
||||
def relative_config_path(self, *path: str | Path) -> Path:
|
||||
path_ = Path(*path).expanduser()
|
||||
return self.config_dir / path_
|
||||
|
||||
@@ -27,3 +27,7 @@ dependencies:
|
||||
version: "1.7.6~1"
|
||||
rules:
|
||||
- if: "target in [esp32s2, esp32s3, esp32p4]"
|
||||
esphome/esp-hub75:
|
||||
version: 0.1.7
|
||||
rules:
|
||||
- if: "target in [esp32, esp32s2, esp32s3, esp32p4]"
|
||||
|
||||
@@ -99,14 +99,11 @@ def storage_should_clean(old: StorageJSON | None, new: StorageJSON) -> bool:
|
||||
|
||||
|
||||
def storage_should_update_cmake_cache(old: StorageJSON, new: StorageJSON) -> bool:
|
||||
if (
|
||||
# ESP32 uses CMake for both Arduino and ESP-IDF frameworks
|
||||
return (
|
||||
old.loaded_integrations != new.loaded_integrations
|
||||
or old.loaded_platforms != new.loaded_platforms
|
||||
) and new.core_platform == PLATFORM_ESP32:
|
||||
from esphome.components.esp32 import FRAMEWORK_ESP_IDF
|
||||
|
||||
return new.framework == FRAMEWORK_ESP_IDF
|
||||
return False
|
||||
) and new.core_platform == PLATFORM_ESP32
|
||||
|
||||
|
||||
def update_storage_json() -> None:
|
||||
|
||||
@@ -156,7 +156,6 @@ lib_deps =
|
||||
esphome/ESP32-audioI2S@2.3.0 ; i2s_audio
|
||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||
esphome/esp-audio-libs@2.0.1 ; audio
|
||||
esphome/esp-hub75@0.1.6 ; hub75
|
||||
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
@@ -180,7 +179,6 @@ lib_deps =
|
||||
droscy/esp_wireguard@0.4.2 ; wireguard
|
||||
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
||||
esphome/esp-audio-libs@2.0.1 ; audio
|
||||
esphome/esp-hub75@0.1.6 ; hub75
|
||||
build_flags =
|
||||
${common:idf.build_flags}
|
||||
-Wno-nonnull-compare
|
||||
|
||||
@@ -4,6 +4,7 @@ from esphome.core import CORE
|
||||
|
||||
def test_require_wake_loop_threadsafe__first_call() -> None:
|
||||
"""Test that first call sets up define and consumes socket."""
|
||||
CORE.config = {"wifi": True}
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
# Verify CORE.data was updated
|
||||
@@ -17,6 +18,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None:
|
||||
"""Test that subsequent calls are idempotent."""
|
||||
# Set up initial state as if already called
|
||||
CORE.data[socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True
|
||||
CORE.config = {"ethernet": True}
|
||||
|
||||
# Call again - should not raise or fail
|
||||
socket.require_wake_loop_threadsafe()
|
||||
@@ -31,6 +33,7 @@ def test_require_wake_loop_threadsafe__idempotent() -> None:
|
||||
def test_require_wake_loop_threadsafe__multiple_calls() -> None:
|
||||
"""Test that multiple calls only set up once."""
|
||||
# Call three times
|
||||
CORE.config = {"openthread": True}
|
||||
socket.require_wake_loop_threadsafe()
|
||||
socket.require_wake_loop_threadsafe()
|
||||
socket.require_wake_loop_threadsafe()
|
||||
@@ -40,3 +43,35 @@ def test_require_wake_loop_threadsafe__multiple_calls() -> None:
|
||||
|
||||
# Verify the define was added (only once, but we can just check it exists)
|
||||
assert any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines)
|
||||
|
||||
|
||||
def test_require_wake_loop_threadsafe__no_networking() -> None:
|
||||
"""Test that wake loop is NOT configured when no networking is configured."""
|
||||
# Set up config without any networking components
|
||||
CORE.config = {"esphome": {"name": "test"}, "logger": {}}
|
||||
|
||||
# Call require_wake_loop_threadsafe
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
# Verify CORE.data flag was NOT set (since has_networking returns False)
|
||||
assert socket.KEY_WAKE_LOOP_THREADSAFE_REQUIRED not in CORE.data
|
||||
|
||||
# Verify the define was NOT added
|
||||
assert not any(d.name == "USE_WAKE_LOOP_THREADSAFE" for d in CORE.defines)
|
||||
|
||||
|
||||
def test_require_wake_loop_threadsafe__no_networking_does_not_consume_socket() -> None:
|
||||
"""Test that no socket is consumed when no networking is configured."""
|
||||
# Set up config without any networking components
|
||||
CORE.config = {"logger": {}}
|
||||
|
||||
# Track initial socket consumer state
|
||||
initial_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {})
|
||||
|
||||
# Call require_wake_loop_threadsafe
|
||||
socket.require_wake_loop_threadsafe()
|
||||
|
||||
# Verify no socket was consumed
|
||||
consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {})
|
||||
assert "socket.wake_loop_threadsafe" not in consumers
|
||||
assert consumers == initial_consumers
|
||||
|
||||
@@ -718,3 +718,65 @@ class TestEsphomeCore:
|
||||
# Even though "web_server" is in loaded_integrations due to the platform,
|
||||
# web_port must return None because the full web_server component is not configured
|
||||
assert target.web_port is None
|
||||
|
||||
def test_has_at_least_one_component__none_configured(self, target):
|
||||
"""Test has_at_least_one_component returns False when none of the components are configured."""
|
||||
target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}}
|
||||
|
||||
assert target.has_at_least_one_component("wifi", "ethernet") is False
|
||||
|
||||
def test_has_at_least_one_component__one_configured(self, target):
|
||||
"""Test has_at_least_one_component returns True when one component is configured."""
|
||||
target.config = {const.CONF_WIFI: {}, "logger": {}}
|
||||
|
||||
assert target.has_at_least_one_component("wifi", "ethernet") is True
|
||||
|
||||
def test_has_at_least_one_component__multiple_configured(self, target):
|
||||
"""Test has_at_least_one_component returns True when multiple components are configured."""
|
||||
target.config = {
|
||||
const.CONF_WIFI: {},
|
||||
const.CONF_ETHERNET: {},
|
||||
"logger": {},
|
||||
}
|
||||
|
||||
assert (
|
||||
target.has_at_least_one_component("wifi", "ethernet", "bluetooth") is True
|
||||
)
|
||||
|
||||
def test_has_at_least_one_component__single_component(self, target):
|
||||
"""Test has_at_least_one_component works with a single component."""
|
||||
target.config = {const.CONF_MQTT: {}}
|
||||
|
||||
assert target.has_at_least_one_component("mqtt") is True
|
||||
assert target.has_at_least_one_component("wifi") is False
|
||||
|
||||
def test_has_at_least_one_component__config_not_loaded(self, target):
|
||||
"""Test has_at_least_one_component raises ValueError when config is not loaded."""
|
||||
target.config = None
|
||||
|
||||
with pytest.raises(ValueError, match="Config has not been loaded yet"):
|
||||
target.has_at_least_one_component("wifi")
|
||||
|
||||
def test_has_networking__with_wifi(self, target):
|
||||
"""Test has_networking returns True when wifi is configured."""
|
||||
target.config = {const.CONF_WIFI: {}}
|
||||
|
||||
assert target.has_networking is True
|
||||
|
||||
def test_has_networking__with_ethernet(self, target):
|
||||
"""Test has_networking returns True when ethernet is configured."""
|
||||
target.config = {const.CONF_ETHERNET: {}}
|
||||
|
||||
assert target.has_networking is True
|
||||
|
||||
def test_has_networking__with_openthread(self, target):
|
||||
"""Test has_networking returns True when openthread is configured."""
|
||||
target.config = {const.CONF_OPENTHREAD: {}}
|
||||
|
||||
assert target.has_networking is True
|
||||
|
||||
def test_has_networking__without_networking(self, target):
|
||||
"""Test has_networking returns False when no networking component is configured."""
|
||||
target.config = {const.CONF_ESPHOME: {"name": "test"}, "logger": {}}
|
||||
|
||||
assert target.has_networking is False
|
||||
|
||||
@@ -9,6 +9,13 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.const import (
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RTL87XX,
|
||||
)
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.storage_json import StorageJSON
|
||||
from esphome.writer import (
|
||||
@@ -21,6 +28,7 @@ from esphome.writer import (
|
||||
clean_build,
|
||||
clean_cmake_cache,
|
||||
storage_should_clean,
|
||||
storage_should_update_cmake_cache,
|
||||
update_storage_json,
|
||||
write_cpp,
|
||||
write_gitignore,
|
||||
@@ -164,6 +172,86 @@ def test_storage_edge_case_from_empty_integrations(
|
||||
assert storage_should_clean(old, new) is False
|
||||
|
||||
|
||||
# Tests for storage_should_update_cmake_cache
|
||||
|
||||
|
||||
@pytest.mark.parametrize("framework", ["arduino", "esp-idf"])
|
||||
def test_storage_should_update_cmake_cache_when_integration_added_esp32(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
framework: str,
|
||||
) -> None:
|
||||
"""Test cmake cache update triggered when integration added on ESP32."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework=framework,
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi", "restart"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework=framework,
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is True
|
||||
|
||||
|
||||
def test_storage_should_update_cmake_cache_when_platform_changed_esp32(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
) -> None:
|
||||
"""Test cmake cache update triggered when platforms change on ESP32."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
loaded_platforms={"sensor"},
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
loaded_platforms={"sensor", "binary_sensor"},
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is True
|
||||
|
||||
|
||||
def test_storage_should_not_update_cmake_cache_when_nothing_changes(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
) -> None:
|
||||
"""Test cmake cache not updated when nothing changes."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=PLATFORM_ESP32,
|
||||
framework="arduino",
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"core_platform",
|
||||
[PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_BK72XX, PLATFORM_RTL87XX],
|
||||
)
|
||||
def test_storage_should_not_update_cmake_cache_for_non_esp32(
|
||||
create_storage: Callable[..., StorageJSON],
|
||||
core_platform: str,
|
||||
) -> None:
|
||||
"""Test cmake cache not updated for non-ESP32 platforms."""
|
||||
old = create_storage(
|
||||
loaded_integrations=["api", "wifi"],
|
||||
core_platform=core_platform,
|
||||
framework="arduino",
|
||||
)
|
||||
new = create_storage(
|
||||
loaded_integrations=["api", "wifi", "restart"],
|
||||
core_platform=core_platform,
|
||||
framework="arduino",
|
||||
)
|
||||
assert storage_should_update_cmake_cache(old, new) is False
|
||||
|
||||
|
||||
@patch("esphome.writer.clean_build")
|
||||
@patch("esphome.writer.StorageJSON")
|
||||
@patch("esphome.writer.storage_path")
|
||||
|
||||
Reference in New Issue
Block a user