mirror of
https://github.com/esphome/esphome.git
synced 2026-09-30 00:10:22 +00:00
Merge remote-tracking branch 'upstream/dev' into 20260218-zigbee-proxy
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -35,3 +35,99 @@ button:
|
||||
data: [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef]
|
||||
- cc1101.send_packet: !lambda |-
|
||||
return {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
|
||||
|
||||
- cc1101.set_frequency: !lambda |-
|
||||
return 433.91e6;
|
||||
- cc1101.set_frequency:
|
||||
value: "433.91MHz"
|
||||
- cc1101.set_frequency:
|
||||
value: 433911000
|
||||
- cc1101.set_frequency: 433912000
|
||||
|
||||
- cc1101.set_output_power: !lambda |-
|
||||
return -29.9;
|
||||
- cc1101.set_output_power:
|
||||
value: "-28"
|
||||
- cc1101.set_output_power:
|
||||
value: 10
|
||||
- cc1101.set_output_power: 11
|
||||
|
||||
- cc1101.set_modulation_type: !lambda |-
|
||||
return cc1101::Modulation::MODULATION_2_FSK;
|
||||
- cc1101.set_modulation_type:
|
||||
value: "4-FSK"
|
||||
- cc1101.set_modulation_type: "GFSK"
|
||||
|
||||
- cc1101.set_symbol_rate: !lambda |-
|
||||
return 6000.0;
|
||||
- cc1101.set_symbol_rate:
|
||||
value: "7000.0"
|
||||
- cc1101.set_symbol_rate:
|
||||
value: 8000.0
|
||||
- cc1101.set_symbol_rate: 9000
|
||||
|
||||
- cc1101.set_rx_attenuation: !lambda |-
|
||||
return cc1101::RxAttenuation::RX_ATTENUATION_0DB;
|
||||
- cc1101.set_rx_attenuation:
|
||||
value: "6dB"
|
||||
- cc1101.set_rx_attenuation: "12dB"
|
||||
|
||||
- cc1101.set_dc_blocking_filter: !lambda |-
|
||||
return false;
|
||||
- cc1101.set_dc_blocking_filter:
|
||||
value: true
|
||||
- cc1101.set_dc_blocking_filter: false
|
||||
|
||||
- cc1101.set_manchester: !lambda |-
|
||||
return false;
|
||||
- cc1101.set_manchester:
|
||||
value: true
|
||||
- cc1101.set_manchester: false
|
||||
|
||||
- cc1101.set_filter_bandwidth: !lambda |-
|
||||
return 58e3;
|
||||
- cc1101.set_filter_bandwidth:
|
||||
value: "59kHz"
|
||||
- cc1101.set_filter_bandwidth:
|
||||
value: 60000
|
||||
- cc1101.set_filter_bandwidth: "61kHz"
|
||||
|
||||
- cc1101.set_fsk_deviation: !lambda |-
|
||||
return 1.5e3;
|
||||
- cc1101.set_fsk_deviation:
|
||||
value: "1.6kHz"
|
||||
- cc1101.set_fsk_deviation:
|
||||
value: 1700
|
||||
- cc1101.set_fsk_deviation: "1.8kHz"
|
||||
|
||||
- cc1101.set_msk_deviation: !lambda |-
|
||||
return 1;
|
||||
- cc1101.set_msk_deviation:
|
||||
value: "2"
|
||||
- cc1101.set_msk_deviation:
|
||||
value: 3
|
||||
- cc1101.set_msk_deviation: "4"
|
||||
|
||||
- cc1101.set_channel: !lambda |-
|
||||
return 0;
|
||||
- cc1101.set_channel:
|
||||
value: "1"
|
||||
- cc1101.set_channel:
|
||||
value: 3
|
||||
- cc1101.set_channel: 3
|
||||
|
||||
- cc1101.set_channel_spacing: !lambda |-
|
||||
return 25e3;
|
||||
- cc1101.set_channel_spacing:
|
||||
value: "26kHz"
|
||||
- cc1101.set_channel_spacing:
|
||||
value: 27000
|
||||
- cc1101.set_channel_spacing: "28kHz"
|
||||
|
||||
- cc1101.set_if_frequency: !lambda |-
|
||||
return 25e3;
|
||||
- cc1101.set_if_frequency:
|
||||
value: "26kHz"
|
||||
- cc1101.set_if_frequency:
|
||||
value: 27000
|
||||
- cc1101.set_if_frequency: "28kHz"
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- lambda: |-
|
||||
// Test deprecated std::string overload still compiles
|
||||
std::string key = "00112233445566778899aabbccddeeff";
|
||||
id(dsmr_instance).set_decryption_key(key);
|
||||
|
||||
dsmr:
|
||||
id: dsmr_instance
|
||||
decryption_key: 00112233445566778899aabbccddeeff
|
||||
max_telegram_length: 1000
|
||||
request_pin: ${request_pin}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
esp32:
|
||||
variant: esp32p4
|
||||
engineering_sample: true
|
||||
flash_size: 32MB
|
||||
cpu_frequency: 400MHz
|
||||
framework:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
then:
|
||||
- hdc302x.heater_on:
|
||||
id: hdc302x_sensor
|
||||
power: QUARTER
|
||||
duration: 5s
|
||||
- hdc302x.heater_off:
|
||||
id: hdc302x_sensor
|
||||
|
||||
sensor:
|
||||
- platform: hdc302x
|
||||
id: hdc302x_sensor
|
||||
i2c_id: i2c_bus
|
||||
temperature:
|
||||
name: Temperature
|
||||
humidity:
|
||||
name: Humidity
|
||||
update_interval: 15s
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -90,6 +90,19 @@ text_sensor:
|
||||
id: ha_hello_world_text2
|
||||
attribute: some_attribute
|
||||
|
||||
event:
|
||||
- platform: template
|
||||
name: Test Event
|
||||
id: test_event
|
||||
event_types:
|
||||
- test_event_type
|
||||
on_event:
|
||||
- homeassistant.event:
|
||||
event: esphome.test_event
|
||||
data:
|
||||
event_name: !lambda |-
|
||||
return event_type;
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
on_time:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "esphome/components/ld2450/ld2450.h"
|
||||
#include "esphome/components/uart/uart_component.h"
|
||||
|
||||
namespace esphome::ld2450::testing {
|
||||
|
||||
// Mock UART component to satisfy UARTDevice parent requirement.
|
||||
class MockUARTComponent : public uart::UARTComponent {
|
||||
public:
|
||||
void write_array(const uint8_t *data, size_t len) override {}
|
||||
MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override));
|
||||
MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override));
|
||||
MOCK_METHOD(size_t, available, (), (override));
|
||||
MOCK_METHOD(void, flush, (), (override));
|
||||
MOCK_METHOD(void, check_logger_conflict, (), (override));
|
||||
};
|
||||
|
||||
// Expose protected members for testing.
|
||||
class TestableLD2450 : public LD2450Component {
|
||||
public:
|
||||
using LD2450Component::buffer_data_;
|
||||
using LD2450Component::buffer_pos_;
|
||||
using LD2450Component::readline_;
|
||||
|
||||
void feed(const std::vector<uint8_t> &data) {
|
||||
for (uint8_t byte : data) {
|
||||
this->readline_(byte);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// LD2450 periodic data frame: header (4) + 3 targets * 8 bytes + footer (2) = 30 bytes
|
||||
// All-zero targets means no presence detected.
|
||||
inline std::vector<uint8_t> make_periodic_frame(uint8_t fill = 0x00) {
|
||||
std::vector<uint8_t> frame = {0xAA, 0xFF, 0x03, 0x00}; // DATA_FRAME_HEADER
|
||||
for (int i = 0; i < 24; i++) {
|
||||
frame.push_back(fill); // 3 targets * 8 bytes
|
||||
}
|
||||
frame.push_back(0x55); // DATA_FRAME_FOOTER
|
||||
frame.push_back(0xCC);
|
||||
return frame;
|
||||
}
|
||||
|
||||
// LD2450 command ACK frame for CMD_ENABLE_CONF (0xFF), successful.
|
||||
// header (4) + length (2) + command (2) + result (2) + footer (4) = 14 bytes
|
||||
inline std::vector<uint8_t> make_ack_frame() {
|
||||
return {
|
||||
0xFD, 0xFC, 0xFB, 0xFA, // CMD_FRAME_HEADER
|
||||
0x04, 0x00, // length = 4
|
||||
0xFF, 0x01, // command = enable_conf, status = success
|
||||
0x00, 0x00, // result = ok
|
||||
0x04, 0x03, 0x02, 0x01 // CMD_FRAME_FOOTER
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace esphome::ld2450::testing
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "common.h"
|
||||
|
||||
namespace esphome::ld2450::testing {
|
||||
|
||||
class LD2450ReadlineTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
this->ld2450_.set_uart_parent(&this->mock_uart_);
|
||||
// Ensure clean state
|
||||
ASSERT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
MockUARTComponent mock_uart_;
|
||||
TestableLD2450 ld2450_;
|
||||
};
|
||||
|
||||
// --- Good data tests ---
|
||||
|
||||
TEST_F(LD2450ReadlineTest, ValidPeriodicFrame) {
|
||||
auto frame = make_periodic_frame();
|
||||
this->ld2450_.feed(frame);
|
||||
// After a complete valid frame, buffer should be reset
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LD2450ReadlineTest, ValidCommandAckFrame) {
|
||||
auto frame = make_ack_frame();
|
||||
this->ld2450_.feed(frame);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LD2450ReadlineTest, BackToBackPeriodicFrames) {
|
||||
auto frame = make_periodic_frame();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
this->ld2450_.feed(frame);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0) << "Frame " << i << " not processed";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LD2450ReadlineTest, BackToBackMixedFrames) {
|
||||
auto periodic = make_periodic_frame();
|
||||
auto ack = make_ack_frame();
|
||||
this->ld2450_.feed(periodic);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
this->ld2450_.feed(ack);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
this->ld2450_.feed(periodic);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
// --- Garbage then valid frame tests ---
|
||||
|
||||
TEST_F(LD2450ReadlineTest, GarbageThenValidFrame) {
|
||||
// Garbage bytes accumulate in the buffer but don't match any footer.
|
||||
// A valid frame follows; its footer resets the buffer and resyncs.
|
||||
std::vector<uint8_t> garbage = {0x01, 0x02, 0x03, 0x42, 0x99};
|
||||
this->ld2450_.feed(garbage);
|
||||
EXPECT_GT(this->ld2450_.buffer_pos_, 0); // Garbage accumulated
|
||||
|
||||
auto frame = make_periodic_frame();
|
||||
this->ld2450_.feed(frame);
|
||||
// Footer from the valid frame resyncs the parser
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
// --- Footer-based resynchronization tests ---
|
||||
|
||||
TEST_F(LD2450ReadlineTest, FooterInGarbageResyncs) {
|
||||
// Garbage containing a periodic frame footer (0x55 0xCC) triggers
|
||||
// a buffer reset, allowing the next frame to be parsed cleanly.
|
||||
std::vector<uint8_t> garbage_with_footer = {0x01, 0x02, 0x03, 0x04, 0x55, 0xCC};
|
||||
this->ld2450_.feed(garbage_with_footer);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0); // Footer reset the buffer
|
||||
|
||||
auto frame = make_periodic_frame();
|
||||
this->ld2450_.feed(frame);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LD2450ReadlineTest, CmdFooterInGarbageResyncs) {
|
||||
// Garbage containing a command frame footer (04 03 02 01) also resyncs.
|
||||
std::vector<uint8_t> garbage_with_footer = {0x10, 0x20, 0x30, 0x40, 0x04, 0x03, 0x02, 0x01};
|
||||
this->ld2450_.feed(garbage_with_footer);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
|
||||
auto frame = make_periodic_frame();
|
||||
this->ld2450_.feed(frame);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
// --- Overflow recovery tests ---
|
||||
|
||||
TEST_F(LD2450ReadlineTest, OverflowResetsBuffer) {
|
||||
// Fill the buffer to capacity with filler that won't match any footer.
|
||||
// MAX_LINE_LENGTH is 45, usable is 44. The 45th byte triggers overflow.
|
||||
std::vector<uint8_t> overflow_data(MAX_LINE_LENGTH, 0x11);
|
||||
this->ld2450_.feed(overflow_data);
|
||||
// After overflow, buffer_pos_ resets to 0 (via the < 4 early return path)
|
||||
EXPECT_LT(this->ld2450_.buffer_pos_, 4);
|
||||
}
|
||||
|
||||
TEST_F(LD2450ReadlineTest, OverflowThenValidFrame) {
|
||||
// Overflow, then a valid frame should be processed.
|
||||
std::vector<uint8_t> overflow_data(MAX_LINE_LENGTH, 0x11);
|
||||
this->ld2450_.feed(overflow_data);
|
||||
|
||||
auto frame = make_periodic_frame();
|
||||
this->ld2450_.feed(frame);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LD2450ReadlineTest, BufferLargeEnoughForDesyncedFooter) {
|
||||
// The key fix: the buffer (45) is large enough that a desynced periodic frame's
|
||||
// footer (at most 30 bytes into the stream) will land inside the buffer before overflow.
|
||||
// Simulate starting 10 bytes into a periodic frame, then a full frame follows.
|
||||
std::vector<uint8_t> mid_frame = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
|
||||
// Then a complete periodic frame whose footer will land at position 40 (10 + 30),
|
||||
// well within the buffer size of 45.
|
||||
auto frame = make_periodic_frame();
|
||||
mid_frame.insert(mid_frame.end(), frame.begin(), frame.end());
|
||||
|
||||
this->ld2450_.feed(mid_frame);
|
||||
// The footer from the frame should have triggered a reset
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
TEST_F(LD2450ReadlineTest, SimulatedRestartThenFrames) {
|
||||
// Simulate LD2450 restart: burst of garbage followed by valid periodic frames.
|
||||
// The garbage + first frame should fit in the buffer so the footer resyncs.
|
||||
std::vector<uint8_t> restart_noise = {
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 8 bytes of mid-frame data
|
||||
};
|
||||
auto frame = make_periodic_frame();
|
||||
// 8 garbage + 30 frame = 38 bytes, well within buffer of 45
|
||||
restart_noise.insert(restart_noise.end(), frame.begin(), frame.end());
|
||||
|
||||
this->ld2450_.feed(restart_noise);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
|
||||
// Subsequent frames should work normally
|
||||
this->ld2450_.feed(frame);
|
||||
EXPECT_EQ(this->ld2450_.buffer_pos_, 0);
|
||||
}
|
||||
|
||||
} // namespace esphome::ld2450::testing
|
||||
@@ -5,4 +5,6 @@ esphome:
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
wait_for_cdc: true
|
||||
early_message: true
|
||||
task_log_buffer_size: 0
|
||||
|
||||
@@ -13,10 +13,10 @@ esphome:
|
||||
on_boot:
|
||||
- priority: 100
|
||||
then:
|
||||
- max7129digit.invert_off:
|
||||
- max7129digit.invert_on:
|
||||
- max7129digit.turn_on:
|
||||
- max7129digit.turn_off:
|
||||
- max7129digit.reverse_on:
|
||||
- max7129digit.reverse_off:
|
||||
- max7129digit.intensity: 10
|
||||
- max7219digit.invert_off:
|
||||
- max7219digit.invert_on:
|
||||
- max7219digit.turn_on:
|
||||
- max7219digit.turn_off:
|
||||
- max7219digit.reverse_on:
|
||||
- max7219digit.reverse_off:
|
||||
- max7219digit.intensity: 10
|
||||
|
||||
@@ -66,12 +66,12 @@ def test_require_wake_loop_threadsafe__no_networking_does_not_consume_socket() -
|
||||
CORE.config = {"logger": {}}
|
||||
|
||||
# Track initial socket consumer state
|
||||
initial_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS, {})
|
||||
initial_udp = CORE.data.get(socket.KEY_SOCKET_CONSUMERS_UDP, {})
|
||||
|
||||
# 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
|
||||
udp_consumers = CORE.data.get(socket.KEY_SOCKET_CONSUMERS_UDP, {})
|
||||
assert "socket.wake_loop_threadsafe" not in udp_consumers
|
||||
assert udp_consumers == initial_udp
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
wifi:
|
||||
band_mode: 5GHZ
|
||||
|
||||
packages:
|
||||
- !include common.yaml
|
||||
Reference in New Issue
Block a user