mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'api_errors' into integration
This commit is contained in:
		| @@ -86,8 +86,8 @@ void APIConnection::start() { | ||||
|   APIError err = this->helper_->init(); | ||||
|   if (err != APIError::OK) { | ||||
|     on_fatal_error(); | ||||
|     ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|              api_error_to_str(err), errno); | ||||
|     ESP_LOGW(TAG, "%s: Helper init failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err), | ||||
|              errno); | ||||
|     return; | ||||
|   } | ||||
|   this->client_info_ = helper_->getpeername(); | ||||
| @@ -119,7 +119,7 @@ void APIConnection::loop() { | ||||
|   APIError err = this->helper_->loop(); | ||||
|   if (err != APIError::OK) { | ||||
|     on_fatal_error(); | ||||
|     ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|     ESP_LOGW(TAG, "%s: Socket operation failed %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|              api_error_to_str(err), errno); | ||||
|     return; | ||||
|   } | ||||
| @@ -136,14 +136,8 @@ void APIConnection::loop() { | ||||
|         break; | ||||
|       } else if (err != APIError::OK) { | ||||
|         on_fatal_error(); | ||||
|         if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { | ||||
|           ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); | ||||
|         } else if (err == APIError::CONNECTION_CLOSED) { | ||||
|           ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str()); | ||||
|         } else { | ||||
|           ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|                    api_error_to_str(err), errno); | ||||
|         } | ||||
|         ESP_LOGW(TAG, "%s: Reading failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err), | ||||
|                  errno); | ||||
|         return; | ||||
|       } else { | ||||
|         this->last_traffic_ = now; | ||||
| @@ -1612,7 +1606,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | ||||
|   APIError err = this->helper_->loop(); | ||||
|   if (err != APIError::OK) { | ||||
|     on_fatal_error(); | ||||
|     ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|     ESP_LOGW(TAG, "%s: Socket operation failed %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|              api_error_to_str(err), errno); | ||||
|     return false; | ||||
|   } | ||||
| @@ -1633,12 +1627,8 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { | ||||
|     return false; | ||||
|   if (err != APIError::OK) { | ||||
|     on_fatal_error(); | ||||
|     if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { | ||||
|       ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); | ||||
|     } else { | ||||
|     ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|              api_error_to_str(err), errno); | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|   // Do not set last_traffic_ on send | ||||
| @@ -1646,11 +1636,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { | ||||
| } | ||||
| void APIConnection::on_unauthenticated_access() { | ||||
|   this->on_fatal_error(); | ||||
|   ESP_LOGD(TAG, "%s requested access without authentication", this->get_client_combined_info().c_str()); | ||||
|   ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str()); | ||||
| } | ||||
| void APIConnection::on_no_setup_connection() { | ||||
|   this->on_fatal_error(); | ||||
|   ESP_LOGD(TAG, "%s requested access without full connection", this->get_client_combined_info().c_str()); | ||||
|   ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str()); | ||||
| } | ||||
| void APIConnection::on_fatal_error() { | ||||
|   this->helper_->close(); | ||||
| @@ -1815,12 +1805,8 @@ void APIConnection::process_batch_() { | ||||
|       this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, packet_info); | ||||
|   if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||
|     on_fatal_error(); | ||||
|     if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { | ||||
|       ESP_LOGW(TAG, "%s: Connection reset during batch write", this->get_client_combined_info().c_str()); | ||||
|     } else { | ||||
|       ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), | ||||
|                api_error_to_str(err), errno); | ||||
|     } | ||||
|     ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err), | ||||
|              errno); | ||||
|   } | ||||
|  | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   | ||||
| @@ -426,7 +426,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) { | ||||
|   ESP_LOGD(TAG, "Noise PSK saved"); | ||||
|   if (make_active) { | ||||
|     this->set_timeout(100, [this, psk]() { | ||||
|       ESP_LOGW(TAG, "Disconnecting all clients to reset connections"); | ||||
|       ESP_LOGW(TAG, "Disconnecting all clients to reset PSK"); | ||||
|       this->set_noise_psk(psk); | ||||
|       for (auto &c : this->clients_) { | ||||
|         c->send_message(DisconnectRequest()); | ||||
|   | ||||
| @@ -1,11 +1,16 @@ | ||||
| import logging | ||||
|  | ||||
| from esphome import pins | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import binary_sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_PIN | ||||
| from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN | ||||
| from esphome.core import CORE | ||||
|  | ||||
| from .. import gpio_ns | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| GPIOBinarySensor = gpio_ns.class_( | ||||
|     "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component | ||||
| ) | ||||
| @@ -41,6 +46,22 @@ async def to_code(config): | ||||
|     pin = await cg.gpio_pin_expression(config[CONF_PIN]) | ||||
|     cg.add(var.set_pin(pin)) | ||||
|  | ||||
|     cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT])) | ||||
|     if config[CONF_USE_INTERRUPT]: | ||||
|     # Check for ESP8266 GPIO16 interrupt limitation | ||||
|     # GPIO16 on ESP8266 is a special pin that doesn't support interrupts through | ||||
|     # the Arduino attachInterrupt() function. This is the only known GPIO pin | ||||
|     # across all supported platforms that has this limitation, so we handle it | ||||
|     # here instead of in the platform-specific code. | ||||
|     use_interrupt = config[CONF_USE_INTERRUPT] | ||||
|     if use_interrupt and CORE.is_esp8266 and config[CONF_PIN][CONF_NUMBER] == 16: | ||||
|         _LOGGER.warning( | ||||
|             "GPIO binary_sensor '%s': GPIO16 on ESP8266 doesn't support interrupts. " | ||||
|             "Falling back to polling mode (same as in ESPHome <2025.7). " | ||||
|             "The sensor will work exactly as before, but other pins have better " | ||||
|             "performance with interrupts.", | ||||
|             config.get(CONF_NAME, config[CONF_ID]), | ||||
|         ) | ||||
|         use_interrupt = False | ||||
|  | ||||
|     cg.add(var.set_use_interrupt(use_interrupt)) | ||||
|     if use_interrupt: | ||||
|         cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) | ||||
|   | ||||
| @@ -29,9 +29,9 @@ from ..defines import ( | ||||
| ) | ||||
| from ..helpers import add_lv_use, lvgl_components_required | ||||
| from ..lv_validation import ( | ||||
|     angle, | ||||
|     get_end_value, | ||||
|     get_start_value, | ||||
|     lv_angle, | ||||
|     lv_bool, | ||||
|     lv_color, | ||||
|     lv_float, | ||||
| @@ -162,7 +162,7 @@ SCALE_SCHEMA = cv.Schema( | ||||
|         cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_, | ||||
|         cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_, | ||||
|         cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360), | ||||
|         cv.Optional(CONF_ROTATION): angle, | ||||
|         cv.Optional(CONF_ROTATION): lv_angle, | ||||
|         cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA), | ||||
|     } | ||||
| ) | ||||
| @@ -187,7 +187,7 @@ class MeterType(WidgetType): | ||||
|         for scale_conf in config.get(CONF_SCALES, ()): | ||||
|             rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2 | ||||
|             if CONF_ROTATION in scale_conf: | ||||
|                 rotation = scale_conf[CONF_ROTATION] // 10 | ||||
|                 rotation = await lv_angle.process(scale_conf[CONF_ROTATION]) | ||||
|             with LocalVariable( | ||||
|                 "meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var) | ||||
|             ) as meter_var: | ||||
| @@ -205,21 +205,20 @@ class MeterType(WidgetType): | ||||
|                         var, | ||||
|                         meter_var, | ||||
|                         ticks[CONF_COUNT], | ||||
|                         ticks[CONF_WIDTH], | ||||
|                         ticks[CONF_LENGTH], | ||||
|                         await size.process(ticks[CONF_WIDTH]), | ||||
|                         await size.process(ticks[CONF_LENGTH]), | ||||
|                         color, | ||||
|                     ) | ||||
|                     if CONF_MAJOR in ticks: | ||||
|                         major = ticks[CONF_MAJOR] | ||||
|                         color = await lv_color.process(major[CONF_COLOR]) | ||||
|                         lv.meter_set_scale_major_ticks( | ||||
|                             var, | ||||
|                             meter_var, | ||||
|                             major[CONF_STRIDE], | ||||
|                             major[CONF_WIDTH], | ||||
|                             major[CONF_LENGTH], | ||||
|                             color, | ||||
|                             major[CONF_LABEL_GAP], | ||||
|                             await size.process(major[CONF_WIDTH]), | ||||
|                             await size.process(major[CONF_LENGTH]), | ||||
|                             await lv_color.process(major[CONF_COLOR]), | ||||
|                             await size.process(major[CONF_LABEL_GAP]), | ||||
|                         ) | ||||
|                 for indicator in scale_conf.get(CONF_INDICATORS, ()): | ||||
|                     (t, v) = next(iter(indicator.items())) | ||||
| @@ -233,7 +232,11 @@ class MeterType(WidgetType): | ||||
|                         lv_assign( | ||||
|                             ivar, | ||||
|                             lv_expr.meter_add_needle_line( | ||||
|                                 var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD] | ||||
|                                 var, | ||||
|                                 meter_var, | ||||
|                                 await size.process(v[CONF_WIDTH]), | ||||
|                                 color, | ||||
|                                 await size.process(v[CONF_R_MOD]), | ||||
|                             ), | ||||
|                         ) | ||||
|                     if t == CONF_ARC: | ||||
| @@ -241,7 +244,11 @@ class MeterType(WidgetType): | ||||
|                         lv_assign( | ||||
|                             ivar, | ||||
|                             lv_expr.meter_add_arc( | ||||
|                                 var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD] | ||||
|                                 var, | ||||
|                                 meter_var, | ||||
|                                 await size.process(v[CONF_WIDTH]), | ||||
|                                 color, | ||||
|                                 await size.process(v[CONF_R_MOD]), | ||||
|                             ), | ||||
|                         ) | ||||
|                     if t == CONF_TICK_STYLE: | ||||
| @@ -257,7 +264,7 @@ class MeterType(WidgetType): | ||||
|                                 color_start, | ||||
|                                 color_end, | ||||
|                                 v[CONF_LOCAL], | ||||
|                                 v[CONF_WIDTH], | ||||
|                                 size.process(v[CONF_WIDTH]), | ||||
|                             ), | ||||
|                         ) | ||||
|                     if t == CONF_IMAGE: | ||||
|   | ||||
							
								
								
									
										69
									
								
								tests/component_tests/gpio/test_gpio_binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								tests/component_tests/gpio/test_gpio_binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| """Tests for the GPIO binary sensor component.""" | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| from collections.abc import Callable | ||||
| from pathlib import Path | ||||
|  | ||||
| import pytest | ||||
|  | ||||
|  | ||||
| def test_gpio_binary_sensor_basic_setup( | ||||
|     generate_main: Callable[[str | Path], str], | ||||
| ) -> None: | ||||
|     """ | ||||
|     When the GPIO binary sensor is set in the yaml file, it should be registered in main | ||||
|     """ | ||||
|     main_cpp = generate_main("tests/component_tests/gpio/test_gpio_binary_sensor.yaml") | ||||
|  | ||||
|     assert "new gpio::GPIOBinarySensor();" in main_cpp | ||||
|     assert "App.register_binary_sensor" in main_cpp | ||||
|     assert "bs_gpio->set_use_interrupt(true);" in main_cpp | ||||
|     assert "bs_gpio->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp | ||||
|  | ||||
|  | ||||
| def test_gpio_binary_sensor_esp8266_gpio16_disables_interrupt( | ||||
|     generate_main: Callable[[str | Path], str], | ||||
|     caplog: pytest.LogCaptureFixture, | ||||
| ) -> None: | ||||
|     """ | ||||
|     Test that ESP8266 GPIO16 automatically disables interrupt mode with a warning | ||||
|     """ | ||||
|     main_cpp = generate_main( | ||||
|         "tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml" | ||||
|     ) | ||||
|  | ||||
|     # Check that interrupt is disabled for GPIO16 | ||||
|     assert "bs_gpio16->set_use_interrupt(false);" in main_cpp | ||||
|  | ||||
|     # Check that the warning was logged | ||||
|     assert "GPIO16 on ESP8266 doesn't support interrupts" in caplog.text | ||||
|     assert "Falling back to polling mode" in caplog.text | ||||
|  | ||||
|  | ||||
| def test_gpio_binary_sensor_esp8266_other_pins_use_interrupt( | ||||
|     generate_main: Callable[[str | Path], str], | ||||
| ) -> None: | ||||
|     """ | ||||
|     Test that ESP8266 pins other than GPIO16 still use interrupt mode | ||||
|     """ | ||||
|     main_cpp = generate_main( | ||||
|         "tests/component_tests/gpio/test_gpio_binary_sensor_esp8266.yaml" | ||||
|     ) | ||||
|  | ||||
|     # GPIO5 should still use interrupts | ||||
|     assert "bs_gpio5->set_use_interrupt(true);" in main_cpp | ||||
|     assert "bs_gpio5->set_interrupt_type(gpio::INTERRUPT_ANY_EDGE);" in main_cpp | ||||
|  | ||||
|  | ||||
| def test_gpio_binary_sensor_explicit_polling_mode( | ||||
|     generate_main: Callable[[str | Path], str], | ||||
| ) -> None: | ||||
|     """ | ||||
|     Test that explicitly setting use_interrupt: false works | ||||
|     """ | ||||
|     main_cpp = generate_main( | ||||
|         "tests/component_tests/gpio/test_gpio_binary_sensor_polling.yaml" | ||||
|     ) | ||||
|  | ||||
|     assert "bs_polling->set_use_interrupt(false);" in main_cpp | ||||
							
								
								
									
										11
									
								
								tests/component_tests/gpio/test_gpio_binary_sensor.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/component_tests/gpio/test_gpio_binary_sensor.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| esphome: | ||||
|   name: test | ||||
|  | ||||
| esp32: | ||||
|   board: esp32dev | ||||
|  | ||||
| binary_sensor: | ||||
|   - platform: gpio | ||||
|     pin: 5 | ||||
|     name: "Test GPIO Binary Sensor" | ||||
|     id: bs_gpio | ||||
| @@ -0,0 +1,20 @@ | ||||
| esphome: | ||||
|   name: test | ||||
|  | ||||
| esp8266: | ||||
|   board: d1_mini | ||||
|  | ||||
| binary_sensor: | ||||
|   - platform: gpio | ||||
|     pin: | ||||
|       number: 16 | ||||
|       mode: INPUT_PULLDOWN_16 | ||||
|     name: "GPIO16 Touch Sensor" | ||||
|     id: bs_gpio16 | ||||
|  | ||||
|   - platform: gpio | ||||
|     pin: | ||||
|       number: 5 | ||||
|       mode: INPUT_PULLUP | ||||
|     name: "GPIO5 Button" | ||||
|     id: bs_gpio5 | ||||
| @@ -0,0 +1,12 @@ | ||||
| esphome: | ||||
|   name: test | ||||
|  | ||||
| esp32: | ||||
|   board: esp32dev | ||||
|  | ||||
| binary_sensor: | ||||
|   - platform: gpio | ||||
|     pin: 5 | ||||
|     name: "Polling Mode Sensor" | ||||
|     id: bs_polling | ||||
|     use_interrupt: false | ||||
| @@ -919,21 +919,21 @@ lvgl: | ||||
|                       text_color: 0xFFFFFF | ||||
|                       scales: | ||||
|                         - ticks: | ||||
|                             width: 1 | ||||
|                             width: !lambda return 1; | ||||
|                             count: 61 | ||||
|                             length: 20 | ||||
|                             length: 20% | ||||
|                             color: 0xFFFFFF | ||||
|                           range_from: 0 | ||||
|                           range_to: 60 | ||||
|                           angle_range: 360 | ||||
|                           rotation: 270 | ||||
|                           rotation: !lambda return 2700; | ||||
|                           indicators: | ||||
|                             - line: | ||||
|                                 opa: 50% | ||||
|                                 id: minute_hand | ||||
|                                 color: 0xFF0000 | ||||
|                                 r_mod: -1 | ||||
|                                 width: 3 | ||||
|                                 r_mod: !lambda return -1; | ||||
|                                 width: !lambda return 3; | ||||
|                         - | ||||
|                           angle_range: 330 | ||||
|                           rotation: 300 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user