Merge remote-tracking branch 'upstream/dev' into remove_posix_tz_parser

# Conflicts:
#	esphome/components/api/api_pb2_dump.cpp
This commit is contained in:
J. Nick Koston
2026-03-25 20:33:26 -10:00
354 changed files with 7932 additions and 3480 deletions
@@ -19,6 +19,7 @@ esp32:
disable_mbedtls_pkcs7: true
disable_regi2c_in_iram: true
disable_fatfs: true
sram1_as_iram: true
wifi:
ssid: MySSID
@@ -0,0 +1,18 @@
ethernet:
type: ENC28J60
clk_pin: 18
mosi_pin: 19
miso_pin: 16
cs_pin: 17
interrupt_pin: 21
reset_pin: 20
manual_ip:
static_ip: 192.168.178.56
gateway: 192.168.178.1
subnet: 255.255.255.0
domain: .local
mac_address: "02:AA:BB:CC:DD:01"
on_connect:
- logger.log: "Ethernet connected!"
on_disconnect:
- logger.log: "Ethernet disconnected!"
@@ -0,0 +1,19 @@
ethernet:
type: ENC28J60
clk_pin: 19
mosi_pin: 21
miso_pin: 23
cs_pin: 18
interrupt_pin: 36
reset_pin: 22
clock_speed: 10Mhz
manual_ip:
static_ip: 192.168.178.56
gateway: 192.168.178.1
subnet: 255.255.255.0
domain: .local
mac_address: "02:AA:BB:CC:DD:01"
on_connect:
- logger.log: "Ethernet connected!"
on_disconnect:
- logger.log: "Ethernet disconnected!"
@@ -0,0 +1,18 @@
ethernet:
type: W5100
clk_pin: 18
mosi_pin: 19
miso_pin: 16
cs_pin: 17
interrupt_pin: 21
reset_pin: 20
manual_ip:
static_ip: 192.168.178.56
gateway: 192.168.178.1
subnet: 255.255.255.0
domain: .local
mac_address: "02:AA:BB:CC:DD:01"
on_connect:
- logger.log: "Ethernet connected!"
on_disconnect:
- logger.log: "Ethernet disconnected!"
@@ -0,0 +1 @@
<<: !include common-enc28j60.yaml
@@ -0,0 +1 @@
<<: !include common-enc28j60-rp2040.yaml
@@ -0,0 +1 @@
<<: !include common-w5100-rp2040.yaml
@@ -8,6 +8,7 @@ infrared:
- platform: ir_rf_proxy
id: ir_rx
name: "IR Receiver"
receiver_frequency: 38kHz
remote_receiver_id: ir_receiver
# RF 900MHz receiver
+1 -1
View File
@@ -16,7 +16,7 @@ class MockUARTComponent : public uart::UARTComponent {
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(uart::FlushResult, flush, (), (override));
MOCK_METHOD(uart::UARTFlushResult, flush, (), (override));
MOCK_METHOD(void, check_logger_conflict, (), (override));
};
+22 -21
View File
@@ -37,11 +37,12 @@ lvgl:
on_resume:
logger.log: LVGL has resumed
on_boot:
logger.log: LVGL has started
- logger.log: LVGL has started
- lvgl.indicator.update:
id: meter_arc_indicator
start_value: 0
end_value: 180
bg_color: light_blue
disp_bg_color: color_id
disp_bg_image: cat_image
disp_bg_opa: cover
bottom_layer:
widgets:
- obj:
@@ -54,7 +55,6 @@ lvgl:
gradients:
- id: color_bar
direction: hor
# dither: err_diff
stops:
- color: 0xFF0000
position: 0
@@ -139,12 +139,11 @@ lvgl:
body:
text: This is a sample messagebox
bg_color: 0x808080
button_style:
bg_color: 0xff00
border_width: 4
buttons:
- id: msgbox_button
text: Button
bg_color: 0x00ff00
border_width: 4
- id: msgbox_apply
text: "Close"
on_click:
@@ -156,8 +155,8 @@ lvgl:
bg_opa: !lambda return 0.5;
- lvgl.image.update:
id: lv_image
zoom: !lambda return 512;
angle: !lambda return 100;
scale: !lambda return 512;
rotation: !lambda return 100;
pivot_x: !lambda return 20;
pivot_y: !lambda return 20;
offset_x: !lambda return 20;
@@ -283,8 +282,8 @@ lvgl:
then:
- lvgl.animimg.stop: anim_img
- lvgl.update:
disp_bg_color: 0xffff00
disp_bg_image: none
bottom_layer:
bg_color: 0xffff00
- lvgl.widget.show: message_box
- label:
text: "Hello shiny day"
@@ -357,8 +356,6 @@ lvgl:
pad_right: 10px
pad_top: 10px
shadow_color: light_blue
shadow_ofs_x: 5
shadow_ofs_y: 5
shadow_opa: cover
shadow_spread: 5
shadow_width: 10
@@ -369,12 +366,10 @@ lvgl:
text_letter_space: 4
text_line_space: 4
text_opa: cover
transform_angle: 180
transform_rotation: 90
transform_height: 100
transform_pivot_x: 50%
transform_pivot_y: 50%
transform_zoom: 0.5
transform_scale: 2.0
transform_scale_x: 1.5
transform_scale_y: 0.8
@@ -466,11 +461,11 @@ lvgl:
id: button_button
width: 20%
height: 10%
transform_angle: !lambda return(180*100);
transform_rotation: !lambda return(180*100);
arc_width: !lambda return 4;
border_width: !lambda return 6;
shadow_ofs_x: !lambda return 6;
shadow_ofs_y: !lambda return 6;
shadow_offset_x: !lambda return 6;
shadow_offset_y: !lambda return 6;
shadow_spread: !lambda return 6;
shadow_width: !lambda return 6;
pressed:
@@ -642,8 +637,8 @@ lvgl:
border_opa: 80%
shadow_color: black
shadow_width: 10
shadow_ofs_x: 5
shadow_ofs_y: 5
shadow_offset_x: 5
shadow_offset_y: 5
shadow_spread: 4
shadow_opa: cover
outline_color: red
@@ -1110,6 +1105,12 @@ lvgl:
color: 0xA0A0A0
length: 80%
opa: 0%
- arc:
id: meter_arc_indicator
color: 0xFF0000
width: 6
start_value: 0
end_value: 360
- id: page3
layout: Horizontal
pad_all: 6px
+30 -17
View File
@@ -273,26 +273,39 @@ text_sensor:
display:
- platform: nextion
id: main_lcd
auto_wake_on_touch: true
brightness: 80%
command_spacing: 5ms
dump_device_info: true
exit_reparse_on_start: true
lambda: |-
ESP_LOGD("display","Display is being tested!");
max_commands_per_loop: 20
max_queue_age: 5000ms # Remove queue items after 5s
max_queue_size: 50
update_interval: 5s
on_sleep:
then:
lambda: 'ESP_LOGD("display","Display went to sleep");'
on_wake:
then:
lambda: 'ESP_LOGD("display","Display woke up");'
on_setup:
then:
lambda: 'ESP_LOGD("display","Display setup completed");'
on_page:
then:
lambda: 'ESP_LOGD("display","Display shows new page %u", x);'
on_buffer_overflow:
then:
logger.log: "Nextion reported a buffer overflow!"
command_spacing: 5ms
dump_device_info: true
max_queue_age: 5000ms # Remove queue items after 5s
on_page:
then:
lambda: 'ESP_LOGD("display","Display shows new page %u", x);'
on_setup:
then:
lambda: 'ESP_LOGD("display","Display setup completed");'
on_sleep:
then:
lambda: 'ESP_LOGD("display","Display went to sleep");'
on_touch:
then:
lambda: |-
ESP_LOGD("display",
"Display was touched at page %u, component %u, touch event: %s",
page_id, component_id, touch_event ? "press" : "release");
on_wake:
then:
lambda: 'ESP_LOGD("display","Display woke up");'
update_interval: 5s
start_up_page: 1
startup_override_ms: 10000ms # Wait 10s for display ready
touch_sleep_timeout: 3
wake_up_page: 2
+13
View File
@@ -0,0 +1,13 @@
number:
- platform: template
name: "Test Number"
id: test_number
optimistic: true
min_value: 0
max_value: 100
step: 1
sensor:
- platform: number
name: "Test Number Value"
source_id: test_number
@@ -0,0 +1,2 @@
packages:
common: !include common.yaml
@@ -0,0 +1,2 @@
packages:
common: !include common.yaml
@@ -5,6 +5,7 @@ namespace esphome::packet_transport::testing {
TEST(PacketTransportBinarySensorTest, AddBinarySensor) {
TestablePacketTransport transport;
binary_sensor::BinarySensor bs;
transport.set_binary_sensor_count(1);
transport.add_binary_sensor("motion", &bs);
ASSERT_EQ(transport.binary_sensors_.size(), 1u);
EXPECT_STREQ(transport.binary_sensors_[0].id, "motion");
@@ -24,6 +25,7 @@ TEST(PacketTransportBinarySensorTest, UnencryptedBinarySensorRoundTrip) {
encoder.init_for_test("sender");
binary_sensor::BinarySensor local_bs;
local_bs.state = true;
encoder.set_binary_sensor_count(1);
encoder.add_binary_sensor("motion", &local_bs);
encoder.send_data_(true);
@@ -46,11 +48,13 @@ TEST(PacketTransportBinarySensorTest, MultipleSensorsRoundTrip) {
sensor::Sensor s1, s2;
s1.state = 10.0f;
s2.state = 20.0f;
encoder.set_sensor_count(2);
encoder.add_sensor("s1", &s1);
encoder.add_sensor("s2", &s2);
binary_sensor::BinarySensor bs1;
bs1.state = true;
encoder.set_binary_sensor_count(1);
encoder.add_binary_sensor("bs1", &bs1);
encoder.send_data_(true);
@@ -5,6 +5,7 @@ namespace esphome::packet_transport::testing {
TEST(PacketTransportSensorTest, AddSensor) {
TestablePacketTransport transport;
sensor::Sensor s;
transport.set_sensor_count(1);
transport.add_sensor("temp", &s);
ASSERT_EQ(transport.sensors_.size(), 1u);
EXPECT_STREQ(transport.sensors_[0].id, "temp");
@@ -26,6 +27,7 @@ TEST(PacketTransportSensorTest, UnencryptedSensorRoundTrip) {
encoder.init_for_test("sender");
sensor::Sensor local_sensor;
local_sensor.state = 42.5f;
encoder.set_sensor_count(1);
encoder.add_sensor("temp", &local_sensor);
encoder.send_data_(true);
@@ -53,6 +55,7 @@ TEST(PacketTransportSensorTest, EncryptedSensorRoundTrip) {
encoder.set_encryption_key(key);
sensor::Sensor local_sensor;
local_sensor.state = 99.9f;
encoder.set_sensor_count(1);
encoder.add_sensor("temp", &local_sensor);
encoder.send_data_(true);
@@ -77,6 +80,7 @@ TEST(PacketTransportSensorTest, SendDataOnlyUpdated) {
sensor::Sensor s1, s2;
s1.state = 1.0f;
s2.state = 2.0f;
encoder.set_sensor_count(2);
encoder.add_sensor("s1", &s1);
encoder.add_sensor("s2", &s2);
@@ -111,6 +115,7 @@ TEST(PacketTransportSensorTest, PingKeyIncludedInTransmittedPacket) {
responder.set_encryption_key(key);
sensor::Sensor local_sensor;
local_sensor.state = 77.7f;
responder.set_sensor_count(1);
responder.add_sensor("temp", &local_sensor);
// Requester sends a MAGIC_PING that the responder processes
@@ -148,6 +153,7 @@ TEST(PacketTransportSensorTest, MissingPingKeyBlocksSensorData) {
responder.set_encryption_key(key);
sensor::Sensor local_sensor;
local_sensor.state = 77.7f;
responder.set_sensor_count(1);
responder.add_sensor("temp", &local_sensor);
responder.send_data_(true);
ASSERT_EQ(responder.sent_packets.size(), 1u);
+4
View File
@@ -6,4 +6,8 @@ sensor:
humidity:
name: SHT4X Humidity
address: 0x44
precision: High
heater_max_duty: 0.02
heater_power: High
heater_time: Long
update_interval: 15s
+15
View File
@@ -0,0 +1,15 @@
sensor:
- platform: spa06_i2c
i2c_id: i2c_bus
address: 0x77
temperature:
id: spa06_i2c_temperature
name: Outside Temperature
sample_rate: 1
oversampling: NONE
pressure:
name: Outside Pressure
id: spa06_i2c_pressure
sample_rate: 25p4
oversampling: 16X
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
+15
View File
@@ -0,0 +1,15 @@
sensor:
- platform: spa06_spi
spi_id: spi_bus
cs_pin: ${cs_pin}
temperature:
id: spa06_spi_temperature
name: Outside Temperature
sample_rate: 1
oversampling: NONE
pressure:
name: Outside Pressure
id: spa06_spi_pressure
sample_rate: 25p4
oversampling: 16X
update_interval: 15s
@@ -0,0 +1,7 @@
substitutions:
cs_pin: GPIO5
packages:
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
cs_pin: GPIO15
packages:
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
cs_pin: GPIO17
packages:
spi: !include ../../test_build_components/common/spi/rp2040-ard.yaml
<<: !include common.yaml
+5
View File
@@ -23,3 +23,8 @@ text:
min_length: 8
max_length: 32
mode: password
text_sensor:
- platform: text
name: "Test Text State"
source_id: test_text
+54 -2
View File
@@ -593,8 +593,6 @@ static time_t esptime_recalc_local(int year, int month, int day, int hour, int m
t.hour = hour;
t.minute = min;
t.second = sec;
t.day_of_week = 1; // Placeholder for fields_in_range()
t.day_of_year = 1;
t.recalc_timestamp_local();
return t.timestamp;
}
@@ -750,6 +748,60 @@ TEST(RecalcTimestampLocal, NonDefaultTransitionTime) {
EXPECT_EQ(esp_result, libc_result);
}
TEST(RecalcTimestampLocal, MinimalFieldsWithoutDayOfWeekOrYear) {
// Regression test for issue #15115: DateTimeEntity::state_as_esptime() constructs
// an ESPTime with only year/month/day/hour/minute/second set (no day_of_week or
// day_of_year). recalc_timestamp_local() must work without those fields.
const char *tz_str = "CET-1CEST,M3.5.0,M10.5.0";
setenv("TZ", tz_str, 1);
tzset();
time::ParsedTimezone tz{};
ASSERT_TRUE(parse_posix_tz(tz_str, tz));
set_global_tz(tz);
// Construct ESPTime with only date/time fields (like state_as_esptime does)
ESPTime t{};
t.year = 2026;
t.month = 3;
t.day_of_month = 20;
t.hour = 23;
t.minute = 14;
t.second = 55;
// day_of_week and day_of_year are deliberately left as 0
t.recalc_timestamp_local();
// Must NOT return -1 (the bug: fields_in_range() rejected valid times)
EXPECT_NE(t.timestamp, -1);
// Verify against libc
time_t libc_result = libc_mktime(2026, 3, 20, 23, 14, 55);
EXPECT_EQ(t.timestamp, libc_result);
}
TEST(RecalcTimestampLocal, MinimalFieldsNoDST) {
// Same test but with a timezone that has no DST
const char *tz_str = "IST-5:30";
setenv("TZ", tz_str, 1);
tzset();
time::ParsedTimezone tz{};
ASSERT_TRUE(parse_posix_tz(tz_str, tz));
set_global_tz(tz);
ESPTime t{};
t.year = 2026;
t.month = 3;
t.day_of_month = 23;
t.hour = 10;
t.minute = 0;
t.second = 0;
t.recalc_timestamp_local();
EXPECT_NE(t.timestamp, -1);
time_t libc_result = libc_mktime(2026, 3, 23, 10, 0, 0);
EXPECT_EQ(t.timestamp, libc_result);
}
TEST(RecalcTimestampLocal, YearBoundaryDST) {
// Test southern hemisphere DST across year boundary
// Australia/Sydney: DST active from October to April (spans Jan 1)
+1 -1
View File
@@ -30,7 +30,7 @@ class MockUARTComponent : public UARTComponent {
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(FlushResult, flush, (), (override));
MOCK_METHOD(UARTFlushResult, flush, (), (override));
MOCK_METHOD(void, check_logger_conflict, (), (override));
};
@@ -0,0 +1,14 @@
uart:
- id: uart_id
tx_pin: PA23
rx_pin: PA18
baud_rate: 9600
data_bits: 8
parity: NONE
stop_bits: 1
switch:
- platform: uart
name: "UART Switch"
uart_id: uart_id
data: [0x01, 0x02, 0x03]