mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into binary_sensor_gpio_polling
This commit is contained in:
		
							
								
								
									
										23
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,28 +1,11 @@ | |||||||
| --- | --- | ||||||
| name: Lock | name: Lock closed issues and PRs | ||||||
|  |  | ||||||
| on: | on: | ||||||
|   schedule: |   schedule: | ||||||
|     - cron: "30 0 * * *" |     - cron: "30 0 * * *"  # Run daily at 00:30 UTC | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|  |  | ||||||
| permissions: |  | ||||||
|   issues: write |  | ||||||
|   pull-requests: write |  | ||||||
|  |  | ||||||
| concurrency: |  | ||||||
|   group: lock |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lock: |   lock: | ||||||
|     runs-on: ubuntu-latest |     uses: esphome/workflows/.github/workflows/lock.yml@main | ||||||
|     steps: |  | ||||||
|       - uses: dessant/lock-threads@v5.0.1 |  | ||||||
|         with: |  | ||||||
|           pr-inactive-days: "1" |  | ||||||
|           pr-lock-reason: "" |  | ||||||
|           exclude-any-pr-labels: keep-open |  | ||||||
|  |  | ||||||
|           issue-inactive-days: "7" |  | ||||||
|           issue-lock-reason: "" |  | ||||||
|           exclude-any-issue-labels: keep-open |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ repos: | |||||||
|       # Run the formatter. |       # Run the formatter. | ||||||
|       - id: ruff-format |       - id: ruff-format | ||||||
|   - repo: https://github.com/PyCQA/flake8 |   - repo: https://github.com/PyCQA/flake8 | ||||||
|     rev: 7.2.0 |     rev: 7.3.0 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: flake8 |       - id: flake8 | ||||||
|         additional_dependencies: |         additional_dependencies: | ||||||
|   | |||||||
| @@ -146,6 +146,7 @@ esphome/components/esp32_ble_client/* @jesserockz | |||||||
| esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz | esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz | ||||||
| esphome/components/esp32_camera_web_server/* @ayufan | esphome/components/esp32_camera_web_server/* @ayufan | ||||||
| esphome/components/esp32_can/* @Sympatron | esphome/components/esp32_can/* @Sympatron | ||||||
|  | esphome/components/esp32_hosted/* @swoboda1337 | ||||||
| esphome/components/esp32_improv/* @jesserockz | esphome/components/esp32_improv/* @jesserockz | ||||||
| esphome/components/esp32_rmt/* @jesserockz | esphome/components/esp32_rmt/* @jesserockz | ||||||
| esphome/components/esp32_rmt_led_strip/* @jesserockz | esphome/components/esp32_rmt_led_strip/* @jesserockz | ||||||
| @@ -323,6 +324,7 @@ esphome/components/one_wire/* @ssieb | |||||||
| esphome/components/online_image/* @clydebarrow @guillempages | esphome/components/online_image/* @clydebarrow @guillempages | ||||||
| esphome/components/opentherm/* @olegtarasov | esphome/components/opentherm/* @olegtarasov | ||||||
| esphome/components/openthread/* @mrene | esphome/components/openthread/* @mrene | ||||||
|  | esphome/components/opt3001/* @ccutrer | ||||||
| esphome/components/ota/* @esphome/core | esphome/components/ota/* @esphome/core | ||||||
| esphome/components/output/* @esphome/core | esphome/components/output/* @esphome/core | ||||||
| esphome/components/packet_transport/* @clydebarrow | esphome/components/packet_transport/* @clydebarrow | ||||||
| @@ -490,7 +492,7 @@ esphome/components/vbus/* @ssieb | |||||||
| esphome/components/veml3235/* @kbx81 | esphome/components/veml3235/* @kbx81 | ||||||
| esphome/components/veml7700/* @latonita | esphome/components/veml7700/* @latonita | ||||||
| esphome/components/version/* @esphome/core | esphome/components/version/* @esphome/core | ||||||
| esphome/components/voice_assistant/* @jesserockz | esphome/components/voice_assistant/* @jesserockz @kahrendt | ||||||
| esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 | esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 | ||||||
| esphome/components/watchdog/* @oarcher | esphome/components/watchdog/* @oarcher | ||||||
| esphome/components/waveshare_epaper/* @clydebarrow | esphome/components/waveshare_epaper/* @clydebarrow | ||||||
|   | |||||||
| @@ -14,8 +14,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@grahambrown11", "@hwstar"] | CODEOWNERS = ["@grahambrown11", "@hwstar"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -149,6 +149,9 @@ _ALARM_CONTROL_PANEL_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def alarm_control_panel_schema( | def alarm_control_panel_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_alarm_control_panel_core_(var, config): | async def setup_alarm_control_panel_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "alarm_control_panel") | ||||||
|     for conf in config.get(CONF_ON_STATE, []): |     for conf in config.get(CONF_ON_STATE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [], conf) | ||||||
|   | |||||||
| @@ -188,6 +188,17 @@ message DeviceInfoRequest { | |||||||
|   // Empty |   // Empty | ||||||
| } | } | ||||||
|  |  | ||||||
|  | message AreaInfo { | ||||||
|  |   uint32 area_id = 1; | ||||||
|  |   string name = 2; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | message DeviceInfo { | ||||||
|  |   uint32 device_id = 1; | ||||||
|  |   string name = 2; | ||||||
|  |   uint32 area_id = 3; | ||||||
|  | } | ||||||
|  |  | ||||||
| message DeviceInfoResponse { | message DeviceInfoResponse { | ||||||
|   option (id) = 10; |   option (id) = 10; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
| @@ -236,6 +247,12 @@ message DeviceInfoResponse { | |||||||
|  |  | ||||||
|   // Supports receiving and saving api encryption key |   // Supports receiving and saving api encryption key | ||||||
|   bool api_encryption_supported = 19; |   bool api_encryption_supported = 19; | ||||||
|  |  | ||||||
|  |   repeated DeviceInfo devices = 20; | ||||||
|  |   repeated AreaInfo areas = 21; | ||||||
|  |  | ||||||
|  |   // Top-level area info to phase out suggested_area | ||||||
|  |   AreaInfo area = 22; | ||||||
| } | } | ||||||
|  |  | ||||||
| message ListEntitiesRequest { | message ListEntitiesRequest { | ||||||
| @@ -280,6 +297,7 @@ message ListEntitiesBinarySensorResponse { | |||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   string icon = 8; |   string icon = 8; | ||||||
|   EntityCategory entity_category = 9; |   EntityCategory entity_category = 9; | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message BinarySensorStateResponse { | message BinarySensorStateResponse { | ||||||
|   option (id) = 21; |   option (id) = 21; | ||||||
| @@ -315,6 +333,7 @@ message ListEntitiesCoverResponse { | |||||||
|   string icon = 10; |   string icon = 10; | ||||||
|   EntityCategory entity_category = 11; |   EntityCategory entity_category = 11; | ||||||
|   bool supports_stop = 12; |   bool supports_stop = 12; | ||||||
|  |   uint32 device_id = 13; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum LegacyCoverState { | enum LegacyCoverState { | ||||||
| @@ -388,6 +407,7 @@ message ListEntitiesFanResponse { | |||||||
|   string icon = 10; |   string icon = 10; | ||||||
|   EntityCategory entity_category = 11; |   EntityCategory entity_category = 11; | ||||||
|   repeated string supported_preset_modes = 12; |   repeated string supported_preset_modes = 12; | ||||||
|  |   uint32 device_id = 13; | ||||||
| } | } | ||||||
| enum FanSpeed { | enum FanSpeed { | ||||||
|   FAN_SPEED_LOW = 0; |   FAN_SPEED_LOW = 0; | ||||||
| @@ -471,6 +491,7 @@ message ListEntitiesLightResponse { | |||||||
|   bool disabled_by_default = 13; |   bool disabled_by_default = 13; | ||||||
|   string icon = 14; |   string icon = 14; | ||||||
|   EntityCategory entity_category = 15; |   EntityCategory entity_category = 15; | ||||||
|  |   uint32 device_id = 16; | ||||||
| } | } | ||||||
| message LightStateResponse { | message LightStateResponse { | ||||||
|   option (id) = 24; |   option (id) = 24; | ||||||
| @@ -563,6 +584,7 @@ message ListEntitiesSensorResponse { | |||||||
|   SensorLastResetType legacy_last_reset_type = 11; |   SensorLastResetType legacy_last_reset_type = 11; | ||||||
|   bool disabled_by_default = 12; |   bool disabled_by_default = 12; | ||||||
|   EntityCategory entity_category = 13; |   EntityCategory entity_category = 13; | ||||||
|  |   uint32 device_id = 14; | ||||||
| } | } | ||||||
| message SensorStateResponse { | message SensorStateResponse { | ||||||
|   option (id) = 25; |   option (id) = 25; | ||||||
| @@ -595,6 +617,7 @@ message ListEntitiesSwitchResponse { | |||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   EntityCategory entity_category = 8; |   EntityCategory entity_category = 8; | ||||||
|   string device_class = 9; |   string device_class = 9; | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message SwitchStateResponse { | message SwitchStateResponse { | ||||||
|   option (id) = 26; |   option (id) = 26; | ||||||
| @@ -632,6 +655,7 @@ message ListEntitiesTextSensorResponse { | |||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message TextSensorStateResponse { | message TextSensorStateResponse { | ||||||
|   option (id) = 27; |   option (id) = 27; | ||||||
| @@ -814,6 +838,7 @@ message ListEntitiesCameraResponse { | |||||||
|   bool disabled_by_default = 5; |   bool disabled_by_default = 5; | ||||||
|   string icon = 6; |   string icon = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
|  |  | ||||||
| message CameraImageResponse { | message CameraImageResponse { | ||||||
| @@ -916,6 +941,7 @@ message ListEntitiesClimateResponse { | |||||||
|   bool supports_target_humidity = 23; |   bool supports_target_humidity = 23; | ||||||
|   float visual_min_humidity = 24; |   float visual_min_humidity = 24; | ||||||
|   float visual_max_humidity = 25; |   float visual_max_humidity = 25; | ||||||
|  |   uint32 device_id = 26; | ||||||
| } | } | ||||||
| message ClimateStateResponse { | message ClimateStateResponse { | ||||||
|   option (id) = 47; |   option (id) = 47; | ||||||
| @@ -999,6 +1025,7 @@ message ListEntitiesNumberResponse { | |||||||
|   string unit_of_measurement = 11; |   string unit_of_measurement = 11; | ||||||
|   NumberMode mode = 12; |   NumberMode mode = 12; | ||||||
|   string device_class = 13; |   string device_class = 13; | ||||||
|  |   uint32 device_id = 14; | ||||||
| } | } | ||||||
| message NumberStateResponse { | message NumberStateResponse { | ||||||
|   option (id) = 50; |   option (id) = 50; | ||||||
| @@ -1039,6 +1066,7 @@ message ListEntitiesSelectResponse { | |||||||
|   repeated string options = 6; |   repeated string options = 6; | ||||||
|   bool disabled_by_default = 7; |   bool disabled_by_default = 7; | ||||||
|   EntityCategory entity_category = 8; |   EntityCategory entity_category = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message SelectStateResponse { | message SelectStateResponse { | ||||||
|   option (id) = 53; |   option (id) = 53; | ||||||
| @@ -1081,6 +1109,7 @@ message ListEntitiesSirenResponse { | |||||||
|   bool supports_duration = 8; |   bool supports_duration = 8; | ||||||
|   bool supports_volume = 9; |   bool supports_volume = 9; | ||||||
|   EntityCategory entity_category = 10; |   EntityCategory entity_category = 10; | ||||||
|  |   uint32 device_id = 11; | ||||||
| } | } | ||||||
| message SirenStateResponse { | message SirenStateResponse { | ||||||
|   option (id) = 56; |   option (id) = 56; | ||||||
| @@ -1144,6 +1173,7 @@ message ListEntitiesLockResponse { | |||||||
|  |  | ||||||
|   // Not yet implemented: |   // Not yet implemented: | ||||||
|   string code_format = 11; |   string code_format = 11; | ||||||
|  |   uint32 device_id = 12; | ||||||
| } | } | ||||||
| message LockStateResponse { | message LockStateResponse { | ||||||
|   option (id) = 59; |   option (id) = 59; | ||||||
| @@ -1183,6 +1213,7 @@ message ListEntitiesButtonResponse { | |||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message ButtonCommandRequest { | message ButtonCommandRequest { | ||||||
|   option (id) = 62; |   option (id) = 62; | ||||||
| @@ -1238,6 +1269,8 @@ message ListEntitiesMediaPlayerResponse { | |||||||
|   bool supports_pause = 8; |   bool supports_pause = 8; | ||||||
|  |  | ||||||
|   repeated MediaPlayerSupportedFormat supported_formats = 9; |   repeated MediaPlayerSupportedFormat supported_formats = 9; | ||||||
|  |  | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message MediaPlayerStateResponse { | message MediaPlayerStateResponse { | ||||||
|   option (id) = 64; |   option (id) = 64; | ||||||
| @@ -1778,6 +1811,7 @@ message ListEntitiesAlarmControlPanelResponse { | |||||||
|   uint32 supported_features = 8; |   uint32 supported_features = 8; | ||||||
|   bool requires_code = 9; |   bool requires_code = 9; | ||||||
|   bool requires_code_to_arm = 10; |   bool requires_code_to_arm = 10; | ||||||
|  |   uint32 device_id = 11; | ||||||
| } | } | ||||||
|  |  | ||||||
| message AlarmControlPanelStateResponse { | message AlarmControlPanelStateResponse { | ||||||
| @@ -1823,6 +1857,7 @@ message ListEntitiesTextResponse { | |||||||
|   uint32 max_length = 9; |   uint32 max_length = 9; | ||||||
|   string pattern = 10; |   string pattern = 10; | ||||||
|   TextMode mode = 11; |   TextMode mode = 11; | ||||||
|  |   uint32 device_id = 12; | ||||||
| } | } | ||||||
| message TextStateResponse { | message TextStateResponse { | ||||||
|   option (id) = 98; |   option (id) = 98; | ||||||
| @@ -1863,6 +1898,7 @@ message ListEntitiesDateResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
| message DateStateResponse { | message DateStateResponse { | ||||||
|   option (id) = 101; |   option (id) = 101; | ||||||
| @@ -1906,6 +1942,7 @@ message ListEntitiesTimeResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
| message TimeStateResponse { | message TimeStateResponse { | ||||||
|   option (id) = 104; |   option (id) = 104; | ||||||
| @@ -1952,6 +1989,7 @@ message ListEntitiesEventResponse { | |||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |  | ||||||
|   repeated string event_types = 9; |   repeated string event_types = 9; | ||||||
|  |   uint32 device_id = 10; | ||||||
| } | } | ||||||
| message EventResponse { | message EventResponse { | ||||||
|   option (id) = 108; |   option (id) = 108; | ||||||
| @@ -1983,6 +2021,7 @@ message ListEntitiesValveResponse { | |||||||
|   bool assumed_state = 9; |   bool assumed_state = 9; | ||||||
|   bool supports_position = 10; |   bool supports_position = 10; | ||||||
|   bool supports_stop = 11; |   bool supports_stop = 11; | ||||||
|  |   uint32 device_id = 12; | ||||||
| } | } | ||||||
|  |  | ||||||
| enum ValveOperation { | enum ValveOperation { | ||||||
| @@ -2029,6 +2068,7 @@ message ListEntitiesDateTimeResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   uint32 device_id = 8; | ||||||
| } | } | ||||||
| message DateTimeStateResponse { | message DateTimeStateResponse { | ||||||
|   option (id) = 113; |   option (id) = 113; | ||||||
| @@ -2069,6 +2109,7 @@ message ListEntitiesUpdateResponse { | |||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|   string device_class = 8; |   string device_class = 8; | ||||||
|  |   uint32 device_id = 9; | ||||||
| } | } | ||||||
| message UpdateStateResponse { | message UpdateStateResponse { | ||||||
|   option (id) = 117; |   option (id) = 117; | ||||||
|   | |||||||
| @@ -28,8 +28,19 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace api { | namespace api { | ||||||
|  |  | ||||||
|  | // Read a maximum of 5 messages per loop iteration to prevent starving other components. | ||||||
|  | // This is a balance between API responsiveness and allowing other components to run. | ||||||
|  | // Since each message could contain multiple protobuf messages when using packet batching, | ||||||
|  | // this limits the number of messages processed, not the number of TCP packets. | ||||||
|  | static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5; | ||||||
|  | static constexpr uint8_t MAX_PING_RETRIES = 60; | ||||||
|  | static constexpr uint16_t PING_RETRY_INTERVAL = 1000; | ||||||
|  | static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2; | ||||||
|  |  | ||||||
| static const char *const TAG = "api.connection"; | static const char *const TAG = "api.connection"; | ||||||
|  | #ifdef USE_ESP32_CAMERA | ||||||
| static const int ESP32_CAMERA_STOP_STREAM = 5000; | static const int ESP32_CAMERA_STOP_STREAM = 5000; | ||||||
|  | #endif | ||||||
|  |  | ||||||
| APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) | ||||||
|     : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { |     : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { | ||||||
| @@ -84,16 +95,6 @@ APIConnection::~APIConnection() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void APIConnection::loop() { | void APIConnection::loop() { | ||||||
|   if (this->remove_) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   if (!network::is_connected()) { |  | ||||||
|     // when network is disconnected force disconnect immediately |  | ||||||
|     // don't wait for timeout |  | ||||||
|     this->on_fatal_error(); |  | ||||||
|     ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->get_client_combined_info().c_str()); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   if (this->next_close_) { |   if (this->next_close_) { | ||||||
|     // requested a disconnect |     // requested a disconnect | ||||||
|     this->helper_->close(); |     this->helper_->close(); | ||||||
| @@ -109,53 +110,56 @@ void APIConnection::loop() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   const uint32_t now = App.get_loop_component_start_time(); | ||||||
|   // Check if socket has data ready before attempting to read |   // Check if socket has data ready before attempting to read | ||||||
|   if (this->helper_->is_socket_ready()) { |   if (this->helper_->is_socket_ready()) { | ||||||
|     ReadPacketBuffer buffer; |     // Read up to MAX_MESSAGES_PER_LOOP messages per loop to improve throughput | ||||||
|     err = this->helper_->read_packet(&buffer); |     for (uint8_t message_count = 0; message_count < MAX_MESSAGES_PER_LOOP; message_count++) { | ||||||
|     if (err == APIError::WOULD_BLOCK) { |       ReadPacketBuffer buffer; | ||||||
|       // pass |       err = this->helper_->read_packet(&buffer); | ||||||
|     } else if (err != APIError::OK) { |       if (err == APIError::WOULD_BLOCK) { | ||||||
|       on_fatal_error(); |         // No more data available | ||||||
|       if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { |         break; | ||||||
|         ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); |       } else if (err != APIError::OK) { | ||||||
|       } else if (err == APIError::CONNECTION_CLOSED) { |         on_fatal_error(); | ||||||
|         ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str()); |         if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { | ||||||
|       } else { |           ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); | ||||||
|         ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(), |         } else if (err == APIError::CONNECTION_CLOSED) { | ||||||
|                  api_error_to_str(err), errno); |           ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str()); | ||||||
|       } |         } else { | ||||||
|       return; |           ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||||
|     } else { |                    api_error_to_str(err), errno); | ||||||
|       this->last_traffic_ = App.get_loop_component_start_time(); |         } | ||||||
|       // read a packet |  | ||||||
|       if (buffer.data_len > 0) { |  | ||||||
|         this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); |  | ||||||
|       } else { |  | ||||||
|         this->read_message(0, buffer.type, nullptr); |  | ||||||
|       } |  | ||||||
|       if (this->remove_) |  | ||||||
|         return; |         return; | ||||||
|  |       } else { | ||||||
|  |         this->last_traffic_ = now; | ||||||
|  |         // read a packet | ||||||
|  |         if (buffer.data_len > 0) { | ||||||
|  |           this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); | ||||||
|  |         } else { | ||||||
|  |           this->read_message(0, buffer.type, nullptr); | ||||||
|  |         } | ||||||
|  |         if (this->remove_) | ||||||
|  |           return; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Process deferred batch if scheduled |   // Process deferred batch if scheduled | ||||||
|   if (this->deferred_batch_.batch_scheduled && |   if (this->deferred_batch_.batch_scheduled && | ||||||
|       App.get_loop_component_start_time() - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { |       now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||||
|     this->process_batch_(); |     this->process_batch_(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!this->list_entities_iterator_.completed()) |   if (!this->list_entities_iterator_.completed()) { | ||||||
|     this->list_entities_iterator_.advance(); |     this->list_entities_iterator_.advance(); | ||||||
|   if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed()) |   } else if (!this->initial_state_iterator_.completed()) { | ||||||
|     this->initial_state_iterator_.advance(); |     this->initial_state_iterator_.advance(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   static uint8_t max_ping_retries = 60; |  | ||||||
|   static uint16_t ping_retry_interval = 1000; |  | ||||||
|   const uint32_t now = App.get_loop_component_start_time(); |  | ||||||
|   if (this->sent_ping_) { |   if (this->sent_ping_) { | ||||||
|     // Disconnect if not responded within 2.5*keepalive |     // Disconnect if not responded within 2.5*keepalive | ||||||
|     if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) { |     if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { | ||||||
|       on_fatal_error(); |       on_fatal_error(); | ||||||
|       ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); |       ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); | ||||||
|     } |     } | ||||||
| @@ -163,17 +167,15 @@ void APIConnection::loop() { | |||||||
|     ESP_LOGVV(TAG, "Sending keepalive PING"); |     ESP_LOGVV(TAG, "Sending keepalive PING"); | ||||||
|     this->sent_ping_ = this->send_message(PingRequest()); |     this->sent_ping_ = this->send_message(PingRequest()); | ||||||
|     if (!this->sent_ping_) { |     if (!this->sent_ping_) { | ||||||
|       this->next_ping_retry_ = now + ping_retry_interval; |       this->next_ping_retry_ = now + PING_RETRY_INTERVAL; | ||||||
|       this->ping_retries_++; |       this->ping_retries_++; | ||||||
|       std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);", |       if (this->ping_retries_ >= MAX_PING_RETRIES) { | ||||||
|                                          this->get_client_combined_info().c_str(), this->ping_retries_); |  | ||||||
|       if (this->ping_retries_ >= max_ping_retries) { |  | ||||||
|         on_fatal_error(); |         on_fatal_error(); | ||||||
|         ESP_LOGE(TAG, "%s disconnecting", warn_str.c_str()); |         ESP_LOGE(TAG, "%s: Ping failed %u times", this->get_client_combined_info().c_str(), this->ping_retries_); | ||||||
|       } else if (this->ping_retries_ >= 10) { |       } else if (this->ping_retries_ >= 10) { | ||||||
|         ESP_LOGW(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval); |         ESP_LOGW(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGD(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval); |         ESP_LOGD(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -197,22 +199,20 @@ void APIConnection::loop() { | |||||||
|     // bool done = 3; |     // bool done = 3; | ||||||
|     buffer.encode_bool(3, done); |     buffer.encode_bool(3, done); | ||||||
|  |  | ||||||
|     bool success = this->send_buffer(buffer, 44); |     bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE); | ||||||
|  |  | ||||||
|     if (success) { |     if (success) { | ||||||
|       this->image_reader_.consume_data(to_send); |       this->image_reader_.consume_data(to_send); | ||||||
|     } |       if (done) { | ||||||
|     if (success && done) { |         this->image_reader_.return_image(); | ||||||
|       this->image_reader_.return_image(); |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   if (state_subs_at_ != -1) { |   if (state_subs_at_ >= 0) { | ||||||
|     const auto &subs = this->parent_->get_state_subs(); |     const auto &subs = this->parent_->get_state_subs(); | ||||||
|     if (state_subs_at_ >= (int) subs.size()) { |     if (state_subs_at_ < static_cast<int>(subs.size())) { | ||||||
|       state_subs_at_ = -1; |  | ||||||
|     } else { |  | ||||||
|       auto &it = subs[state_subs_at_]; |       auto &it = subs[state_subs_at_]; | ||||||
|       SubscribeHomeAssistantStateResponse resp; |       SubscribeHomeAssistantStateResponse resp; | ||||||
|       resp.entity_id = it.entity_id; |       resp.entity_id = it.entity_id; | ||||||
| @@ -221,6 +221,8 @@ void APIConnection::loop() { | |||||||
|       if (this->send_message(resp)) { |       if (this->send_message(resp)) { | ||||||
|         state_subs_at_++; |         state_subs_at_++; | ||||||
|       } |       } | ||||||
|  |     } else { | ||||||
|  |       state_subs_at_ = -1; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -274,6 +276,11 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes | |||||||
|   // Encode directly into buffer |   // Encode directly into buffer | ||||||
|   msg.encode(buffer); |   msg.encode(buffer); | ||||||
|  |  | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   // Log the message for VV debugging | ||||||
|  |   conn->log_send_message_(msg.message_name(), msg.dump()); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   // Calculate actual encoded size (not including header that was already added) |   // Calculate actual encoded size (not including header that was already added) | ||||||
|   size_t actual_payload_size = shared_buf.size() - size_before_encode; |   size_t actual_payload_size = shared_buf.size() - size_before_encode; | ||||||
|  |  | ||||||
| @@ -1430,7 +1437,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe | |||||||
|  |  | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
| void APIConnection::send_event(event::Event *event, const std::string &event_type) { | void APIConnection::send_event(event::Event *event, const std::string &event_type) { | ||||||
|   this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE); |   this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| void APIConnection::send_event_info(event::Event *event) { | void APIConnection::send_event_info(event::Event *event) { | ||||||
|   this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE); |   this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE); | ||||||
| @@ -1619,6 +1626,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_API_NOISE | #ifdef USE_API_NOISE | ||||||
|   resp.api_encryption_supported = true; |   resp.api_encryption_supported = true; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |   for (auto const &device : App.get_devices()) { | ||||||
|  |     DeviceInfo device_info; | ||||||
|  |     device_info.device_id = device->get_device_id(); | ||||||
|  |     device_info.name = device->get_name(); | ||||||
|  |     device_info.area_id = device->get_area_id(); | ||||||
|  |     resp.devices.push_back(device_info); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_AREAS | ||||||
|  |   for (auto const &area : App.get_areas()) { | ||||||
|  |     AreaInfo area_info; | ||||||
|  |     area_info.area_id = area->get_area_id(); | ||||||
|  |     area_info.name = area->get_name(); | ||||||
|  |     resp.areas.push_back(area_info); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
| @@ -1768,7 +1792,8 @@ void APIConnection::process_batch_() { | |||||||
|     const auto &item = this->deferred_batch_.items[0]; |     const auto &item = this->deferred_batch_.items[0]; | ||||||
|  |  | ||||||
|     // Let the creator calculate size and encode if it fits |     // Let the creator calculate size and encode if it fits | ||||||
|     uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true); |     uint16_t payload_size = | ||||||
|  |         item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); | ||||||
|  |  | ||||||
|     if (payload_size > 0 && |     if (payload_size > 0 && | ||||||
|         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { |         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { | ||||||
| @@ -1818,7 +1843,7 @@ void APIConnection::process_batch_() { | |||||||
|   for (const auto &item : this->deferred_batch_.items) { |   for (const auto &item : this->deferred_batch_.items) { | ||||||
|     // Try to encode message |     // Try to encode message | ||||||
|     // The creator will calculate overhead to determine if the message fits |     // The creator will calculate overhead to determine if the message fits | ||||||
|     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false); |     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); | ||||||
|  |  | ||||||
|     if (payload_size == 0) { |     if (payload_size == 0) { | ||||||
|       // Message won't fit, stop processing |       // Message won't fit, stop processing | ||||||
| @@ -1881,21 +1906,23 @@ void APIConnection::process_batch_() { | |||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                                    bool is_single) const { |                                                    bool is_single, uint16_t message_type) const { | ||||||
|   switch (message_type_) { |   if (has_tagged_string_ptr_()) { | ||||||
|     case 0:  // Function pointer |     // Handle string-based messages | ||||||
|       return data_.ptr(entity, conn, remaining_size, is_single); |     switch (message_type) { | ||||||
|  |  | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
|     case EventResponse::MESSAGE_TYPE: { |       case EventResponse::MESSAGE_TYPE: { | ||||||
|       auto *e = static_cast<event::Event *>(entity); |         auto *e = static_cast<event::Event *>(entity); | ||||||
|       return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single); |         return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single); | ||||||
|     } |       } | ||||||
| #endif | #endif | ||||||
|  |       default: | ||||||
|     default: |         // Should not happen, return 0 to indicate no message | ||||||
|       // Should not happen, return 0 to indicate no message |         return 0; | ||||||
|       return 0; |     } | ||||||
|  |   } else { | ||||||
|  |     // Function pointer case | ||||||
|  |     return data_.ptr(entity, conn, remaining_size, is_single); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -301,6 +301,9 @@ class APIConnection : public APIServerConnection { | |||||||
|     response.icon = entity->get_icon(); |     response.icon = entity->get_icon(); | ||||||
|     response.disabled_by_default = entity->is_disabled_by_default(); |     response.disabled_by_default = entity->is_disabled_by_default(); | ||||||
|     response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); |     response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); | ||||||
|  | #ifdef USE_DEVICES | ||||||
|  |     response.device_id = entity->get_device_id(); | ||||||
|  | #endif | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Helper function to fill common entity state fields |   // Helper function to fill common entity state fields | ||||||
| @@ -480,55 +483,57 @@ class APIConnection : public APIServerConnection { | |||||||
|   // Function pointer type for message encoding |   // Function pointer type for message encoding | ||||||
|   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); |   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); | ||||||
|  |  | ||||||
|   // Optimized MessageCreator class using union dispatch |   // Optimized MessageCreator class using tagged pointer | ||||||
|   class MessageCreator { |   class MessageCreator { | ||||||
|  |     // Ensure pointer alignment allows LSB tagging | ||||||
|  |     static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging"); | ||||||
|  |  | ||||||
|    public: |    public: | ||||||
|     // Constructor for function pointer (message_type = 0) |     // Constructor for function pointer | ||||||
|     MessageCreator(MessageCreatorPtr ptr) : message_type_(0) { data_.ptr = ptr; } |     MessageCreator(MessageCreatorPtr ptr) { | ||||||
|  |       // Function pointers are always aligned, so LSB is 0 | ||||||
|  |       data_.ptr = ptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Constructor for string state capture |     // Constructor for string state capture | ||||||
|     MessageCreator(const std::string &value, uint16_t msg_type) : message_type_(msg_type) { |     explicit MessageCreator(const std::string &str_value) { | ||||||
|       data_.string_ptr = new std::string(value); |       // Allocate string and tag the pointer | ||||||
|  |       auto *str = new std::string(str_value); | ||||||
|  |       // Set LSB to 1 to indicate string pointer | ||||||
|  |       data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Destructor |     // Destructor | ||||||
|     ~MessageCreator() { |     ~MessageCreator() { | ||||||
|       // Clean up string data for string-based message types |       if (has_tagged_string_ptr_()) { | ||||||
|       if (uses_string_data_()) { |         delete get_string_ptr_(); | ||||||
|         delete data_.string_ptr; |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Copy constructor |     // Copy constructor | ||||||
|     MessageCreator(const MessageCreator &other) : message_type_(other.message_type_) { |     MessageCreator(const MessageCreator &other) { | ||||||
|       if (message_type_ == 0) { |       if (other.has_tagged_string_ptr_()) { | ||||||
|         data_.ptr = other.data_.ptr; |         auto *str = new std::string(*other.get_string_ptr_()); | ||||||
|       } else if (uses_string_data_()) { |         data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||||
|         data_.string_ptr = new std::string(*other.data_.string_ptr); |  | ||||||
|       } else { |       } else { | ||||||
|         data_ = other.data_;  // For POD types |         data_ = other.data_; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Move constructor |     // Move constructor | ||||||
|     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_), message_type_(other.message_type_) { |     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; } | ||||||
|       other.message_type_ = 0;  // Reset other to function pointer type |  | ||||||
|       other.data_.ptr = nullptr; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Assignment operators (needed for batch deduplication) |     // Assignment operators (needed for batch deduplication) | ||||||
|     MessageCreator &operator=(const MessageCreator &other) { |     MessageCreator &operator=(const MessageCreator &other) { | ||||||
|       if (this != &other) { |       if (this != &other) { | ||||||
|         // Clean up current string data if needed |         // Clean up current string data if needed | ||||||
|         if (uses_string_data_()) { |         if (has_tagged_string_ptr_()) { | ||||||
|           delete data_.string_ptr; |           delete get_string_ptr_(); | ||||||
|         } |         } | ||||||
|         // Copy new data |         // Copy new data | ||||||
|         message_type_ = other.message_type_; |         if (other.has_tagged_string_ptr_()) { | ||||||
|         if (other.message_type_ == 0) { |           auto *str = new std::string(*other.get_string_ptr_()); | ||||||
|           data_.ptr = other.data_.ptr; |           data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; | ||||||
|         } else if (other.uses_string_data_()) { |  | ||||||
|           data_.string_ptr = new std::string(*other.data_.string_ptr); |  | ||||||
|         } else { |         } else { | ||||||
|           data_ = other.data_; |           data_ = other.data_; | ||||||
|         } |         } | ||||||
| @@ -539,30 +544,35 @@ class APIConnection : public APIServerConnection { | |||||||
|     MessageCreator &operator=(MessageCreator &&other) noexcept { |     MessageCreator &operator=(MessageCreator &&other) noexcept { | ||||||
|       if (this != &other) { |       if (this != &other) { | ||||||
|         // Clean up current string data if needed |         // Clean up current string data if needed | ||||||
|         if (uses_string_data_()) { |         if (has_tagged_string_ptr_()) { | ||||||
|           delete data_.string_ptr; |           delete get_string_ptr_(); | ||||||
|         } |         } | ||||||
|         // Move data |         // Move data | ||||||
|         message_type_ = other.message_type_; |  | ||||||
|         data_ = other.data_; |         data_ = other.data_; | ||||||
|         // Reset other to safe state |         // Reset other to safe state | ||||||
|         other.message_type_ = 0; |  | ||||||
|         other.data_.ptr = nullptr; |         other.data_.ptr = nullptr; | ||||||
|       } |       } | ||||||
|       return *this; |       return *this; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Call operator |     // Call operator - now accepts message_type as parameter | ||||||
|     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) const; |     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, | ||||||
|  |                         uint16_t message_type) const; | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     // Helper to check if this message type uses heap-allocated strings |     // Check if this contains a string pointer | ||||||
|     bool uses_string_data_() const { return message_type_ == EventResponse::MESSAGE_TYPE; } |     bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } | ||||||
|     union CreatorData { |  | ||||||
|       MessageCreatorPtr ptr;    // 8 bytes |     // Get the actual string pointer (clears the tag bit) | ||||||
|       std::string *string_ptr;  // 8 bytes |     std::string *get_string_ptr_() const { | ||||||
|     } data_;                    // 8 bytes |       // NOLINTNEXTLINE(performance-no-int-to-ptr) | ||||||
|     uint16_t message_type_;     // 2 bytes (0 = function ptr, >0 = state capture) |       return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     union { | ||||||
|  |       MessageCreatorPtr ptr; | ||||||
|  |       uintptr_t tagged; | ||||||
|  |     } data_;  // 4 bytes on 32-bit | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // Generic batching mechanism for both state updates and entity info |   // Generic batching mechanism for both state updates and entity info | ||||||
|   | |||||||
| @@ -66,6 +66,17 @@ const char *api_error_to_str(APIError err) { | |||||||
|   return "UNKNOWN"; |   return "UNKNOWN"; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Default implementation for loop - handles sending buffered data | ||||||
|  | APIError APIFrameHelper::loop() { | ||||||
|  |   if (!this->tx_buf_.empty()) { | ||||||
|  |     APIError err = try_send_tx_buf_(); | ||||||
|  |     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||||
|  |       return err; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return APIError::OK;  // Convert WOULD_BLOCK to OK to avoid connection termination | ||||||
|  | } | ||||||
|  |  | ||||||
| // Helper method to buffer data from IOVs | // Helper method to buffer data from IOVs | ||||||
| void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { | void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { | ||||||
|   SendBuffer buffer; |   SendBuffer buffer; | ||||||
| @@ -274,17 +285,21 @@ APIError APINoiseFrameHelper::init() { | |||||||
| } | } | ||||||
| /// Run through handshake messages (if in that phase) | /// Run through handshake messages (if in that phase) | ||||||
| APIError APINoiseFrameHelper::loop() { | APIError APINoiseFrameHelper::loop() { | ||||||
|   APIError err = state_action_(); |   // During handshake phase, process as many actions as possible until we can't progress | ||||||
|   if (err != APIError::OK && err != APIError::WOULD_BLOCK) { |   // socket_->ready() stays true until next main loop, but state_action() will return | ||||||
|     return err; |   // WOULD_BLOCK when no more data is available to read | ||||||
|   } |   while (state_ != State::DATA && this->socket_->ready()) { | ||||||
|   if (!this->tx_buf_.empty()) { |     APIError err = state_action_(); | ||||||
|     err = try_send_tx_buf_(); |  | ||||||
|     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { |     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||||
|       return err; |       return err; | ||||||
|     } |     } | ||||||
|  |     if (err == APIError::WOULD_BLOCK) { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|   return APIError::OK;  // Convert WOULD_BLOCK to OK to avoid connection termination |  | ||||||
|  |   // Use base class implementation for buffer sending | ||||||
|  |   return APIFrameHelper::loop(); | ||||||
| } | } | ||||||
|  |  | ||||||
| /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter | /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter | ||||||
| @@ -330,17 +345,15 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (rx_header_buf_[0] != 0x01) { | ||||||
|  |       state_ = State::FAILED; | ||||||
|  |       HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]); | ||||||
|  |       return APIError::BAD_INDICATOR; | ||||||
|  |     } | ||||||
|     // header reading done |     // header reading done | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // read body |   // read body | ||||||
|   uint8_t indicator = rx_header_buf_[0]; |  | ||||||
|   if (indicator != 0x01) { |  | ||||||
|     state_ = State::FAILED; |  | ||||||
|     HELPER_LOG("Bad indicator byte %u", indicator); |  | ||||||
|     return APIError::BAD_INDICATOR; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2]; |   uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2]; | ||||||
|  |  | ||||||
|   if (state_ != State::DATA && msg_size > 128) { |   if (state_ != State::DATA && msg_size > 128) { | ||||||
| @@ -586,10 +599,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { | |||||||
|     return APIError::BAD_DATA_PACKET; |     return APIError::BAD_DATA_PACKET; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // uint16_t type; |  | ||||||
|   // uint16_t data_len; |  | ||||||
|   // uint8_t *data; |  | ||||||
|   // uint8_t *padding;  zero or more bytes to fill up the rest of the packet |  | ||||||
|   uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1]; |   uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1]; | ||||||
|   uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3]; |   uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3]; | ||||||
|   if (data_len > msg_size - 4) { |   if (data_len > msg_size - 4) { | ||||||
| @@ -822,18 +831,12 @@ APIError APIPlaintextFrameHelper::init() { | |||||||
|   state_ = State::DATA; |   state_ = State::DATA; | ||||||
|   return APIError::OK; |   return APIError::OK; | ||||||
| } | } | ||||||
| /// Not used for plaintext |  | ||||||
| APIError APIPlaintextFrameHelper::loop() { | APIError APIPlaintextFrameHelper::loop() { | ||||||
|   if (state_ != State::DATA) { |   if (state_ != State::DATA) { | ||||||
|     return APIError::BAD_STATE; |     return APIError::BAD_STATE; | ||||||
|   } |   } | ||||||
|   if (!this->tx_buf_.empty()) { |   // Use base class implementation for buffer sending | ||||||
|     APIError err = try_send_tx_buf_(); |   return APIFrameHelper::loop(); | ||||||
|     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { |  | ||||||
|       return err; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return APIError::OK;  // Convert WOULD_BLOCK to OK to avoid connection termination |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter | /** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ struct PacketInfo { | |||||||
|       : message_type(type), offset(off), payload_size(size), padding(0) {} |       : message_type(type), offset(off), payload_size(size), padding(0) {} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class APIError : int { | enum class APIError : uint16_t { | ||||||
|   OK = 0, |   OK = 0, | ||||||
|   WOULD_BLOCK = 1001, |   WOULD_BLOCK = 1001, | ||||||
|   BAD_HANDSHAKE_PACKET_LEN = 1002, |   BAD_HANDSHAKE_PACKET_LEN = 1002, | ||||||
| @@ -74,7 +74,7 @@ class APIFrameHelper { | |||||||
|   } |   } | ||||||
|   virtual ~APIFrameHelper() = default; |   virtual ~APIFrameHelper() = default; | ||||||
|   virtual APIError init() = 0; |   virtual APIError init() = 0; | ||||||
|   virtual APIError loop() = 0; |   virtual APIError loop(); | ||||||
|   virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; |   virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; | ||||||
|   bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } |   bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } | ||||||
|   std::string getpeername() { return socket_->getpeername(); } |   std::string getpeername() { return socket_->getpeername(); } | ||||||
|   | |||||||
| @@ -812,6 +812,103 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {} | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } | void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); } | ||||||
| #endif | #endif | ||||||
|  | bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->area_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->name = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void AreaInfo::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_uint32(1, this->area_id); | ||||||
|  |   buffer.encode_string(2, this->name); | ||||||
|  | } | ||||||
|  | void AreaInfo::calculate_size(uint32_t &total_size) const { | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); | ||||||
|  |   ProtoSize::add_string_field(total_size, 1, this->name, false); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void AreaInfo::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("AreaInfo {\n"); | ||||||
|  |   out.append("  area_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->area_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  name: "); | ||||||
|  |   out.append("'").append(this->name).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 3: { | ||||||
|  |       this->area_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool DeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->name = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void DeviceInfo::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_uint32(1, this->device_id); | ||||||
|  |   buffer.encode_string(2, this->name); | ||||||
|  |   buffer.encode_uint32(3, this->area_id); | ||||||
|  | } | ||||||
|  | void DeviceInfo::calculate_size(uint32_t &total_size) const { | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
|  |   ProtoSize::add_string_field(total_size, 1, this->name, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->area_id, false); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void DeviceInfo::dump_to(std::string &out) const { | ||||||
|  |   __attribute__((unused)) char buffer[64]; | ||||||
|  |   out.append("DeviceInfo {\n"); | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  name: "); | ||||||
|  |   out.append("'").append(this->name).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  area_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->area_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
| bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 1: { |     case 1: { | ||||||
| @@ -896,6 +993,18 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v | |||||||
|       this->bluetooth_mac_address = value.as_string(); |       this->bluetooth_mac_address = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 20: { | ||||||
|  |       this->devices.push_back(value.as_message<DeviceInfo>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 21: { | ||||||
|  |       this->areas.push_back(value.as_message<AreaInfo>()); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 22: { | ||||||
|  |       this->area = value.as_message<AreaInfo>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -920,6 +1029,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(16, this->suggested_area); |   buffer.encode_string(16, this->suggested_area); | ||||||
|   buffer.encode_string(18, this->bluetooth_mac_address); |   buffer.encode_string(18, this->bluetooth_mac_address); | ||||||
|   buffer.encode_bool(19, this->api_encryption_supported); |   buffer.encode_bool(19, this->api_encryption_supported); | ||||||
|  |   for (auto &it : this->devices) { | ||||||
|  |     buffer.encode_message<DeviceInfo>(20, it, true); | ||||||
|  |   } | ||||||
|  |   for (auto &it : this->areas) { | ||||||
|  |     buffer.encode_message<AreaInfo>(21, it, true); | ||||||
|  |   } | ||||||
|  |   buffer.encode_message<AreaInfo>(22, this->area); | ||||||
| } | } | ||||||
| void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); |   ProtoSize::add_bool_field(total_size, 1, this->uses_password, false); | ||||||
| @@ -941,6 +1057,9 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 2, this->suggested_area, false); |   ProtoSize::add_string_field(total_size, 2, this->suggested_area, false); | ||||||
|   ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false); |   ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false); |   ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false); | ||||||
|  |   ProtoSize::add_repeated_message(total_size, 2, this->devices); | ||||||
|  |   ProtoSize::add_repeated_message(total_size, 2, this->areas); | ||||||
|  |   ProtoSize::add_message_object(total_size, 2, this->area, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | void DeviceInfoResponse::dump_to(std::string &out) const { | ||||||
| @@ -1026,6 +1145,22 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  api_encryption_supported: "); |   out.append("  api_encryption_supported: "); | ||||||
|   out.append(YESNO(this->api_encryption_supported)); |   out.append(YESNO(this->api_encryption_supported)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->devices) { | ||||||
|  |     out.append("  devices: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   for (const auto &it : this->areas) { | ||||||
|  |     out.append("  areas: "); | ||||||
|  |     it.dump_to(out); | ||||||
|  |     out.append("\n"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   out.append("  area: "); | ||||||
|  |   this->area.dump_to(out); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1052,6 +1187,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1102,6 +1241,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_string(8, this->icon); |   buffer.encode_string(8, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(9, this->entity_category); | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -1113,6 +1253,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); |   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||||
| @@ -1154,6 +1295,11 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1236,6 +1382,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->supports_stop = value.as_bool(); |       this->supports_stop = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 13: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1289,6 +1439,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(10, this->icon); |   buffer.encode_string(10, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(11, this->entity_category); | ||||||
|   buffer.encode_bool(12, this->supports_stop); |   buffer.encode_bool(12, this->supports_stop); | ||||||
|  |   buffer.encode_uint32(13, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -1303,6 +1454,7 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); |   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||||
| @@ -1356,6 +1508,11 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_stop: "); |   out.append("  supports_stop: "); | ||||||
|   out.append(YESNO(this->supports_stop)); |   out.append(YESNO(this->supports_stop)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1565,6 +1722,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 13: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -1620,6 +1781,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   for (auto &it : this->supported_preset_modes) { |   for (auto &it : this->supported_preset_modes) { | ||||||
|     buffer.encode_string(12, it, true); |     buffer.encode_string(12, it, true); | ||||||
|   } |   } | ||||||
|  |   buffer.encode_uint32(13, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -1638,6 +1800,7 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const { | |||||||
|       ProtoSize::add_string_field(total_size, 1, it, true); |       ProtoSize::add_string_field(total_size, 1, it, true); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||||
| @@ -1694,6 +1857,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const { | |||||||
|     out.append("'").append(it).append("'"); |     out.append("'").append(it).append("'"); | ||||||
|     out.append("\n"); |     out.append("\n"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -1987,6 +2155,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 16: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2055,6 +2227,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(13, this->disabled_by_default); |   buffer.encode_bool(13, this->disabled_by_default); | ||||||
|   buffer.encode_string(14, this->icon); |   buffer.encode_string(14, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(15, this->entity_category); | ||||||
|  |   buffer.encode_uint32(16, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -2080,6 +2253,7 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); |   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesLightResponse::dump_to(std::string &out) const { | void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||||
| @@ -2151,6 +2325,11 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2658,6 +2837,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 14: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2716,6 +2899,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); |   buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type); | ||||||
|   buffer.encode_bool(12, this->disabled_by_default); |   buffer.encode_bool(12, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(13, this->entity_category); | ||||||
|  |   buffer.encode_uint32(14, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -2731,6 +2915,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||||
| @@ -2789,6 +2974,11 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -2860,6 +3050,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2910,6 +3104,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||||
|   buffer.encode_string(9, this->device_class); |   buffer.encode_string(9, this->device_class); | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -2921,6 +3116,7 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | ||||||
| @@ -2962,6 +3158,11 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3061,6 +3262,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3110,6 +3315,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|   buffer.encode_string(8, this->device_class); |   buffer.encode_string(8, this->device_class); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -3120,6 +3326,7 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | ||||||
| @@ -3157,6 +3364,11 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3922,6 +4134,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3966,6 +4182,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(5, this->disabled_by_default); |   buffer.encode_bool(5, this->disabled_by_default); | ||||||
|   buffer.encode_string(6, this->icon); |   buffer.encode_string(6, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -3975,6 +4192,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); |   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCameraResponse::dump_to(std::string &out) const { | void ListEntitiesCameraResponse::dump_to(std::string &out) const { | ||||||
| @@ -4008,6 +4226,11 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -4156,6 +4379,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v | |||||||
|       this->supports_target_humidity = value.as_bool(); |       this->supports_target_humidity = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 26: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -4262,6 +4489,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(23, this->supports_target_humidity); |   buffer.encode_bool(23, this->supports_target_humidity); | ||||||
|   buffer.encode_float(24, this->visual_min_humidity); |   buffer.encode_float(24, this->visual_min_humidity); | ||||||
|   buffer.encode_float(25, this->visual_max_humidity); |   buffer.encode_float(25, this->visual_max_humidity); | ||||||
|  |   buffer.encode_uint32(26, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -4313,6 +4541,7 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false); |   ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false); | ||||||
|   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false); |   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false); | ||||||
|   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false); |   ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 2, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||||
| @@ -4436,6 +4665,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | |||||||
|   sprintf(buffer, "%g", this->visual_max_humidity); |   sprintf(buffer, "%g", this->visual_max_humidity); | ||||||
|   out.append(buffer); |   out.append(buffer); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -4901,6 +5135,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->mode = value.as_enum<enums::NumberMode>(); |       this->mode = value.as_enum<enums::NumberMode>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 14: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -4971,6 +5209,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(11, this->unit_of_measurement); |   buffer.encode_string(11, this->unit_of_measurement); | ||||||
|   buffer.encode_enum<enums::NumberMode>(12, this->mode); |   buffer.encode_enum<enums::NumberMode>(12, this->mode); | ||||||
|   buffer.encode_string(13, this->device_class); |   buffer.encode_string(13, this->device_class); | ||||||
|  |   buffer.encode_uint32(14, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -4986,6 +5225,7 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); |   ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesNumberResponse::dump_to(std::string &out) const { | void ListEntitiesNumberResponse::dump_to(std::string &out) const { | ||||||
| @@ -5046,6 +5286,11 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5151,6 +5396,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5202,6 +5451,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   } |   } | ||||||
|   buffer.encode_bool(7, this->disabled_by_default); |   buffer.encode_bool(7, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(8, this->entity_category); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5216,6 +5466,7 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   } |   } | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSelectResponse::dump_to(std::string &out) const { | void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||||
| @@ -5255,6 +5506,11 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5378,6 +5634,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5431,6 +5691,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(8, this->supports_duration); |   buffer.encode_bool(8, this->supports_duration); | ||||||
|   buffer.encode_bool(9, this->supports_volume); |   buffer.encode_bool(9, this->supports_volume); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); | ||||||
|  |   buffer.encode_uint32(11, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5447,6 +5708,7 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSirenResponse::dump_to(std::string &out) const { | void ListEntitiesSirenResponse::dump_to(std::string &out) const { | ||||||
| @@ -5494,6 +5756,11 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5683,6 +5950,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->requires_code = value.as_bool(); |       this->requires_code = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5735,6 +6006,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(9, this->supports_open); |   buffer.encode_bool(9, this->supports_open); | ||||||
|   buffer.encode_bool(10, this->requires_code); |   buffer.encode_bool(10, this->requires_code); | ||||||
|   buffer.encode_string(11, this->code_format); |   buffer.encode_string(11, this->code_format); | ||||||
|  |   buffer.encode_uint32(12, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5748,6 +6020,7 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_open, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_open, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); |   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->code_format, false); |   ProtoSize::add_string_field(total_size, 1, this->code_format, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesLockResponse::dump_to(std::string &out) const { | void ListEntitiesLockResponse::dump_to(std::string &out) const { | ||||||
| @@ -5797,6 +6070,11 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  code_format: "); |   out.append("  code_format: "); | ||||||
|   out.append("'").append(this->code_format).append("'"); |   out.append("'").append(this->code_format).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5922,6 +6200,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -5971,6 +6253,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|   buffer.encode_string(8, this->device_class); |   buffer.encode_string(8, this->device_class); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -5981,6 +6264,7 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesButtonResponse::dump_to(std::string &out) const { | void ListEntitiesButtonResponse::dump_to(std::string &out) const { | ||||||
| @@ -6018,6 +6302,11 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -6135,6 +6424,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI | |||||||
|       this->supports_pause = value.as_bool(); |       this->supports_pause = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -6187,6 +6480,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   for (auto &it : this->supported_formats) { |   for (auto &it : this->supported_formats) { | ||||||
|     buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); |     buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true); | ||||||
|   } |   } | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -6198,6 +6492,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const | |||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false); | ||||||
|   ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); |   ProtoSize::add_repeated_message(total_size, 1, this->supported_formats); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | ||||||
| @@ -6241,6 +6536,11 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const { | |||||||
|     it.dump_to(out); |     it.dump_to(out); | ||||||
|     out.append("\n"); |     out.append("\n"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -8551,6 +8851,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro | |||||||
|       this->requires_code_to_arm = value.as_bool(); |       this->requires_code_to_arm = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -8598,6 +8902,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons | |||||||
|   buffer.encode_uint32(8, this->supported_features); |   buffer.encode_uint32(8, this->supported_features); | ||||||
|   buffer.encode_bool(9, this->requires_code); |   buffer.encode_bool(9, this->requires_code); | ||||||
|   buffer.encode_bool(10, this->requires_code_to_arm); |   buffer.encode_bool(10, this->requires_code_to_arm); | ||||||
|  |   buffer.encode_uint32(11, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -8610,6 +8915,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) | |||||||
|   ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false); |   ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); |   ProtoSize::add_bool_field(total_size, 1, this->requires_code, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false); |   ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | ||||||
| @@ -8656,6 +8962,11 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  requires_code_to_arm: "); |   out.append("  requires_code_to_arm: "); | ||||||
|   out.append(YESNO(this->requires_code_to_arm)); |   out.append(YESNO(this->requires_code_to_arm)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -8783,6 +9094,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->mode = value.as_enum<enums::TextMode>(); |       this->mode = value.as_enum<enums::TextMode>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -8835,6 +9150,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_uint32(9, this->max_length); |   buffer.encode_uint32(9, this->max_length); | ||||||
|   buffer.encode_string(10, this->pattern); |   buffer.encode_string(10, this->pattern); | ||||||
|   buffer.encode_enum<enums::TextMode>(11, this->mode); |   buffer.encode_enum<enums::TextMode>(11, this->mode); | ||||||
|  |   buffer.encode_uint32(12, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -8848,6 +9164,7 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_uint32_field(total_size, 1, this->max_length, false); |   ProtoSize::add_uint32_field(total_size, 1, this->max_length, false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->pattern, false); |   ProtoSize::add_string_field(total_size, 1, this->pattern, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTextResponse::dump_to(std::string &out) const { | void ListEntitiesTextResponse::dump_to(std::string &out) const { | ||||||
| @@ -8899,6 +9216,11 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  mode: "); |   out.append("  mode: "); | ||||||
|   out.append(proto_enum_to_string<enums::TextMode>(this->mode)); |   out.append(proto_enum_to_string<enums::TextMode>(this->mode)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9014,6 +9336,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9058,6 +9384,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9067,6 +9394,7 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); |   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesDateResponse::dump_to(std::string &out) const { | void ListEntitiesDateResponse::dump_to(std::string &out) const { | ||||||
| @@ -9100,6 +9428,11 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9255,6 +9588,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9299,6 +9636,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9308,6 +9646,7 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); |   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTimeResponse::dump_to(std::string &out) const { | void ListEntitiesTimeResponse::dump_to(std::string &out) const { | ||||||
| @@ -9341,6 +9680,11 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9496,6 +9840,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 10: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9552,6 +9900,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   for (auto &it : this->event_types) { |   for (auto &it : this->event_types) { | ||||||
|     buffer.encode_string(9, it, true); |     buffer.encode_string(9, it, true); | ||||||
|   } |   } | ||||||
|  |   buffer.encode_uint32(10, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9567,6 +9916,7 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const { | |||||||
|       ProtoSize::add_string_field(total_size, 1, it, true); |       ProtoSize::add_string_field(total_size, 1, it, true); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesEventResponse::dump_to(std::string &out) const { | void ListEntitiesEventResponse::dump_to(std::string &out) const { | ||||||
| @@ -9610,6 +9960,11 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const { | |||||||
|     out.append("'").append(it).append("'"); |     out.append("'").append(it).append("'"); | ||||||
|     out.append("\n"); |     out.append("\n"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9678,6 +10033,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val | |||||||
|       this->supports_stop = value.as_bool(); |       this->supports_stop = value.as_bool(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9730,6 +10089,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(9, this->assumed_state); |   buffer.encode_bool(9, this->assumed_state); | ||||||
|   buffer.encode_bool(10, this->supports_position); |   buffer.encode_bool(10, this->supports_position); | ||||||
|   buffer.encode_bool(11, this->supports_stop); |   buffer.encode_bool(11, this->supports_stop); | ||||||
|  |   buffer.encode_uint32(12, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9743,6 +10103,7 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); |   ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_position, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_position, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); |   ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesValveResponse::dump_to(std::string &out) const { | void ListEntitiesValveResponse::dump_to(std::string &out) const { | ||||||
| @@ -9792,6 +10153,11 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  supports_stop: "); |   out.append("  supports_stop: "); | ||||||
|   out.append(YESNO(this->supports_stop)); |   out.append(YESNO(this->supports_stop)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -9923,6 +10289,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -9967,6 +10337,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_uint32(8, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -9976,6 +10347,7 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_string_field(total_size, 1, this->icon, false); |   ProtoSize::add_string_field(total_size, 1, this->icon, false); | ||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | ||||||
| @@ -10009,6 +10381,11 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -10114,6 +10491,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 9: { | ||||||
|  |       this->device_id = value.as_uint32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -10163,6 +10544,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|   buffer.encode_string(8, this->device_class); |   buffer.encode_string(8, this->device_class); | ||||||
|  |   buffer.encode_uint32(9, this->device_id); | ||||||
| } | } | ||||||
| void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->object_id, false); |   ProtoSize::add_string_field(total_size, 1, this->object_id, false); | ||||||
| @@ -10173,6 +10555,7 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const { | |||||||
|   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); |   ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false); | ||||||
|   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); |   ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false); | ||||||
|   ProtoSize::add_string_field(total_size, 1, this->device_class, false); |   ProtoSize::add_string_field(total_size, 1, this->device_class, false); | ||||||
|  |   ProtoSize::add_uint32_field(total_size, 1, this->device_id, false); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | ||||||
| @@ -10210,6 +10593,11 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  device_class: "); |   out.append("  device_class: "); | ||||||
|   out.append("'").append(this->device_class).append("'"); |   out.append("'").append(this->device_class).append("'"); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_id: "); | ||||||
|  |   sprintf(buffer, "%" PRIu32, this->device_id); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -19,7 +19,7 @@ class APIServerConnectionBase : public ProtoService { | |||||||
|  |  | ||||||
|   template<typename T> bool send_message(const T &msg) { |   template<typename T> bool send_message(const T &msg) { | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|     this->log_send_message_(T::message_name(), msg.dump()); |     this->log_send_message_(msg.message_name(), msg.dump()); | ||||||
| #endif | #endif | ||||||
|     return this->send_message_(msg, T::MESSAGE_TYPE); |     return this->send_message_(msg, T::MESSAGE_TYPE); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -47,6 +47,11 @@ void APIServer::setup() { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   // Schedule reboot if no clients connect within timeout | ||||||
|  |   if (this->reboot_timeout_ != 0) { | ||||||
|  |     this->schedule_reboot_timeout_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0);  // monitored for incoming connections |   this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0);  // monitored for incoming connections | ||||||
|   if (this->socket_ == nullptr) { |   if (this->socket_ == nullptr) { | ||||||
|     ESP_LOGW(TAG, "Could not create socket"); |     ESP_LOGW(TAG, "Could not create socket"); | ||||||
| @@ -106,8 +111,6 @@ void APIServer::setup() { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   this->last_connected_ = App.get_loop_component_start_time(); |  | ||||||
|  |  | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
|   if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { |   if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { | ||||||
|     esp32_camera::global_esp32_camera->add_image_callback( |     esp32_camera::global_esp32_camera->add_image_callback( | ||||||
| @@ -121,6 +124,16 @@ void APIServer::setup() { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void APIServer::schedule_reboot_timeout_() { | ||||||
|  |   this->status_set_warning(); | ||||||
|  |   this->set_timeout("api_reboot", this->reboot_timeout_, []() { | ||||||
|  |     if (!global_api_server->is_connected()) { | ||||||
|  |       ESP_LOGE(TAG, "No clients; rebooting"); | ||||||
|  |       App.reboot(); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
| void APIServer::loop() { | void APIServer::loop() { | ||||||
|   // Accept new clients only if the socket exists and has incoming connections |   // Accept new clients only if the socket exists and has incoming connections | ||||||
|   if (this->socket_ && this->socket_->ready()) { |   if (this->socket_ && this->socket_->ready()) { | ||||||
| @@ -130,51 +143,61 @@ void APIServer::loop() { | |||||||
|       auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len); |       auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len); | ||||||
|       if (!sock) |       if (!sock) | ||||||
|         break; |         break; | ||||||
|       ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str()); |       ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str()); | ||||||
|  |  | ||||||
|       auto *conn = new APIConnection(std::move(sock), this); |       auto *conn = new APIConnection(std::move(sock), this); | ||||||
|       this->clients_.emplace_back(conn); |       this->clients_.emplace_back(conn); | ||||||
|       conn->start(); |       conn->start(); | ||||||
|  |  | ||||||
|  |       // Clear warning status and cancel reboot when first client connects | ||||||
|  |       if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) { | ||||||
|  |         this->status_clear_warning(); | ||||||
|  |         this->cancel_timeout("api_reboot"); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (this->clients_.empty()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Process clients and remove disconnected ones in a single pass |   // Process clients and remove disconnected ones in a single pass | ||||||
|   if (!this->clients_.empty()) { |   // Check network connectivity once for all clients | ||||||
|     size_t client_index = 0; |   if (!network::is_connected()) { | ||||||
|     while (client_index < this->clients_.size()) { |     // Network is down - disconnect all clients | ||||||
|       auto &client = this->clients_[client_index]; |     for (auto &client : this->clients_) { | ||||||
|  |       client->on_fatal_error(); | ||||||
|       if (client->remove_) { |       ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str()); | ||||||
|         // Handle disconnection |  | ||||||
|         this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_); |  | ||||||
|         ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str()); |  | ||||||
|  |  | ||||||
|         // Swap with the last element and pop (avoids expensive vector shifts) |  | ||||||
|         if (client_index < this->clients_.size() - 1) { |  | ||||||
|           std::swap(this->clients_[client_index], this->clients_.back()); |  | ||||||
|         } |  | ||||||
|         this->clients_.pop_back(); |  | ||||||
|         // Don't increment client_index since we need to process the swapped element |  | ||||||
|       } else { |  | ||||||
|         // Process active client |  | ||||||
|         client->loop(); |  | ||||||
|         client_index++;  // Move to next client |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|  |     // Continue to process and clean up the clients below | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->reboot_timeout_ != 0) { |   size_t client_index = 0; | ||||||
|     const uint32_t now = App.get_loop_component_start_time(); |   while (client_index < this->clients_.size()) { | ||||||
|     if (!this->is_connected()) { |     auto &client = this->clients_[client_index]; | ||||||
|       if (now - this->last_connected_ > this->reboot_timeout_) { |  | ||||||
|         ESP_LOGE(TAG, "No client connected; rebooting"); |     if (!client->remove_) { | ||||||
|         App.reboot(); |       // Common case: process active client | ||||||
|       } |       client->loop(); | ||||||
|       this->status_set_warning(); |       client_index++; | ||||||
|     } else { |       continue; | ||||||
|       this->last_connected_ = now; |  | ||||||
|       this->status_clear_warning(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Rare case: handle disconnection | ||||||
|  |     this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_); | ||||||
|  |     ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str()); | ||||||
|  |  | ||||||
|  |     // Swap with the last element and pop (avoids expensive vector shifts) | ||||||
|  |     if (client_index < this->clients_.size() - 1) { | ||||||
|  |       std::swap(this->clients_[client_index], this->clients_.back()); | ||||||
|  |     } | ||||||
|  |     this->clients_.pop_back(); | ||||||
|  |  | ||||||
|  |     // Schedule reboot when last client disconnects | ||||||
|  |     if (this->clients_.empty() && this->reboot_timeout_ != 0) { | ||||||
|  |       this->schedule_reboot_timeout_(); | ||||||
|  |     } | ||||||
|  |     // Don't increment client_index since we need to process the swapped element | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -142,6 +142,7 @@ class APIServer : public Component, public Controller { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   void schedule_reboot_timeout_(); | ||||||
|   // Pointers and pointer-like types first (4 bytes each) |   // Pointers and pointer-like types first (4 bytes each) | ||||||
|   std::unique_ptr<socket::Socket> socket_ = nullptr; |   std::unique_ptr<socket::Socket> socket_ = nullptr; | ||||||
|   Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>(); |   Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>(); | ||||||
| @@ -150,7 +151,6 @@ class APIServer : public Component, public Controller { | |||||||
|   // 4-byte aligned types |   // 4-byte aligned types | ||||||
|   uint32_t reboot_timeout_{300000}; |   uint32_t reboot_timeout_{300000}; | ||||||
|   uint32_t batch_delay_{100}; |   uint32_t batch_delay_{100}; | ||||||
|   uint32_t last_connected_{0}; |  | ||||||
|  |  | ||||||
|   // Vectors and strings (12 bytes each on 32-bit) |   // Vectors and strings (12 bytes each on 32-bit) | ||||||
|   std::vector<std::unique_ptr<APIConnection>> clients_; |   std::vector<std::unique_ptr<APIConnection>> clients_; | ||||||
|   | |||||||
| @@ -335,6 +335,7 @@ class ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   std::string dump() const; |   std::string dump() const; | ||||||
|   virtual void dump_to(std::string &out) const = 0; |   virtual void dump_to(std::string &out) const = 0; | ||||||
|  |   virtual const char *message_name() const { return "unknown"; } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
| @@ -312,7 +312,7 @@ FileDecoderState AudioDecoder::decode_mp3_() { | |||||||
|   if (err) { |   if (err) { | ||||||
|     switch (err) { |     switch (err) { | ||||||
|       case esp_audio_libs::helix_decoder::ERR_MP3_OUT_OF_MEMORY: |       case esp_audio_libs::helix_decoder::ERR_MP3_OUT_OF_MEMORY: | ||||||
|         // Intentional fallthrough |         [[fallthrough]]; | ||||||
|       case esp_audio_libs::helix_decoder::ERR_MP3_NULL_POINTER: |       case esp_audio_libs::helix_decoder::ERR_MP3_NULL_POINTER: | ||||||
|         return FileDecoderState::FAILED; |         return FileDecoderState::FAILED; | ||||||
|         break; |         break; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE | #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE | ||||||
| #include "esp_crt_bundle.h" | #include "esp_crt_bundle.h" | ||||||
| @@ -16,13 +17,13 @@ namespace audio { | |||||||
| static const uint32_t READ_WRITE_TIMEOUT_MS = 20; | static const uint32_t READ_WRITE_TIMEOUT_MS = 20; | ||||||
|  |  | ||||||
| static const uint32_t CONNECTION_TIMEOUT_MS = 5000; | static const uint32_t CONNECTION_TIMEOUT_MS = 5000; | ||||||
|  | static const uint8_t MAX_FETCHING_HEADER_ATTEMPTS = 6; | ||||||
| // The number of times the http read times out with no data before throwing an error |  | ||||||
| static const uint32_t ERROR_COUNT_NO_DATA_READ_TIMEOUT = 100; |  | ||||||
|  |  | ||||||
| static const size_t HTTP_STREAM_BUFFER_SIZE = 2048; | static const size_t HTTP_STREAM_BUFFER_SIZE = 2048; | ||||||
|  |  | ||||||
| static const uint8_t MAX_REDIRECTION = 5; | static const uint8_t MAX_REDIRECTIONS = 5; | ||||||
|  |  | ||||||
|  | static const char *const TAG = "audio_reader"; | ||||||
|  |  | ||||||
| // Some common HTTP status codes - borrowed from http_request component accessed 20241224 | // Some common HTTP status codes - borrowed from http_request component accessed 20241224 | ||||||
| enum HttpStatus { | enum HttpStatus { | ||||||
| @@ -94,7 +95,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { | |||||||
|   client_config.url = uri.c_str(); |   client_config.url = uri.c_str(); | ||||||
|   client_config.cert_pem = nullptr; |   client_config.cert_pem = nullptr; | ||||||
|   client_config.disable_auto_redirect = false; |   client_config.disable_auto_redirect = false; | ||||||
|   client_config.max_redirection_count = 10; |   client_config.max_redirection_count = MAX_REDIRECTIONS; | ||||||
|   client_config.event_handler = http_event_handler; |   client_config.event_handler = http_event_handler; | ||||||
|   client_config.user_data = this; |   client_config.user_data = this; | ||||||
|   client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE; |   client_config.buffer_size = HTTP_STREAM_BUFFER_SIZE; | ||||||
| @@ -116,12 +117,29 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { | |||||||
|   esp_err_t err = esp_http_client_open(this->client_, 0); |   esp_err_t err = esp_http_client_open(this->client_, 0); | ||||||
|  |  | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to open URL"); | ||||||
|     this->cleanup_connection_(); |     this->cleanup_connection_(); | ||||||
|     return err; |     return err; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   int64_t header_length = esp_http_client_fetch_headers(this->client_); |   int64_t header_length = esp_http_client_fetch_headers(this->client_); | ||||||
|  |   uint8_t reattempt_count = 0; | ||||||
|  |   while ((header_length < 0) && (reattempt_count < MAX_FETCHING_HEADER_ATTEMPTS)) { | ||||||
|  |     this->cleanup_connection_(); | ||||||
|  |     if (header_length != -ESP_ERR_HTTP_EAGAIN) { | ||||||
|  |       // Serious error, no recovery | ||||||
|  |       return ESP_FAIL; | ||||||
|  |     } else { | ||||||
|  |       // Reconnect from a fresh state to avoid a bug where it never reads the headers even if made available | ||||||
|  |       this->client_ = esp_http_client_init(&client_config); | ||||||
|  |       esp_http_client_open(this->client_, 0); | ||||||
|  |       header_length = esp_http_client_fetch_headers(this->client_); | ||||||
|  |       ++reattempt_count; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (header_length < 0) { |   if (header_length < 0) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to fetch headers"); | ||||||
|     this->cleanup_connection_(); |     this->cleanup_connection_(); | ||||||
|     return ESP_FAIL; |     return ESP_FAIL; | ||||||
|   } |   } | ||||||
| @@ -135,7 +153,7 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) { | |||||||
|  |  | ||||||
|   ssize_t redirect_count = 0; |   ssize_t redirect_count = 0; | ||||||
|  |  | ||||||
|   while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTION)) { |   while ((esp_http_client_set_redirection(this->client_) == ESP_OK) && (redirect_count < MAX_REDIRECTIONS)) { | ||||||
|     err = esp_http_client_open(this->client_, 0); |     err = esp_http_client_open(this->client_, 0); | ||||||
|     if (err != ESP_OK) { |     if (err != ESP_OK) { | ||||||
|       this->cleanup_connection_(); |       this->cleanup_connection_(); | ||||||
| @@ -267,27 +285,29 @@ AudioReaderState AudioReader::http_read_() { | |||||||
|       return AudioReaderState::FINISHED; |       return AudioReaderState::FINISHED; | ||||||
|     } |     } | ||||||
|   } else if (this->output_transfer_buffer_->free() > 0) { |   } else if (this->output_transfer_buffer_->free() > 0) { | ||||||
|     size_t bytes_to_read = this->output_transfer_buffer_->free(); |     int received_len = esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), | ||||||
|     int received_len = |                                             this->output_transfer_buffer_->free()); | ||||||
|         esp_http_client_read(this->client_, (char *) this->output_transfer_buffer_->get_buffer_end(), bytes_to_read); |  | ||||||
|  |  | ||||||
|     if (received_len > 0) { |     if (received_len > 0) { | ||||||
|       this->output_transfer_buffer_->increase_buffer_length(received_len); |       this->output_transfer_buffer_->increase_buffer_length(received_len); | ||||||
|       this->last_data_read_ms_ = millis(); |       this->last_data_read_ms_ = millis(); | ||||||
|     } else if (received_len < 0) { |       return AudioReaderState::READING; | ||||||
|  |     } else if (received_len <= 0) { | ||||||
|       // HTTP read error |       // HTTP read error | ||||||
|       this->cleanup_connection_(); |       if (received_len == -1) { | ||||||
|       return AudioReaderState::FAILED; |         // A true connection error occured, no chance at recovery | ||||||
|     } else { |         this->cleanup_connection_(); | ||||||
|       if (bytes_to_read > 0) { |         return AudioReaderState::FAILED; | ||||||
|         // Read timed out |  | ||||||
|         if ((millis() - this->last_data_read_ms_) > CONNECTION_TIMEOUT_MS) { |  | ||||||
|           this->cleanup_connection_(); |  | ||||||
|           return AudioReaderState::FAILED; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         delay(READ_WRITE_TIMEOUT_MS); |  | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Read timed out, manually verify if it has been too long since the last successful read | ||||||
|  |       if ((millis() - this->last_data_read_ms_) > MAX_FETCHING_HEADER_ATTEMPTS * CONNECTION_TIMEOUT_MS) { | ||||||
|  |         ESP_LOGE(TAG, "Timed out"); | ||||||
|  |         this->cleanup_connection_(); | ||||||
|  |         return AudioReaderState::FAILED; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       delay(READ_WRITE_TIMEOUT_MS); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,11 +7,13 @@ | |||||||
|  |  | ||||||
| extern "C" { | extern "C" { | ||||||
| #include "rtos_pub.h" | #include "rtos_pub.h" | ||||||
| #include "spi.h" | // rtos_pub.h must be included before the rest of the includes | ||||||
|  |  | ||||||
| #include "arm_arch.h" | #include "arm_arch.h" | ||||||
| #include "general_dma_pub.h" | #include "general_dma_pub.h" | ||||||
| #include "gpio_pub.h" | #include "gpio_pub.h" | ||||||
| #include "icu_pub.h" | #include "icu_pub.h" | ||||||
|  | #include "spi.h" | ||||||
| #undef SPI_DAT | #undef SPI_DAT | ||||||
| #undef SPI_BASE | #undef SPI_BASE | ||||||
| }; | }; | ||||||
| @@ -124,7 +126,7 @@ void BekenSPILEDStripLightOutput::setup() { | |||||||
|   size_t buffer_size = this->get_buffer_size_(); |   size_t buffer_size = this->get_buffer_size_(); | ||||||
|   size_t dma_buffer_size = (buffer_size * 8) + (2 * 64); |   size_t dma_buffer_size = (buffer_size * 8) + (2 * 64); | ||||||
|  |  | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator; | ||||||
|   this->buf_ = allocator.allocate(buffer_size); |   this->buf_ = allocator.allocate(buffer_size); | ||||||
|   if (this->buf_ == nullptr) { |   if (this->buf_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Cannot allocate LED buffer!"); |     ESP_LOGE(TAG, "Cannot allocate LED buffer!"); | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | |||||||
|   // turn on (after one-shot sensor automatically powers down) |   // turn on (after one-shot sensor automatically powers down) | ||||||
|   uint8_t turn_on = BH1750_COMMAND_POWER_ON; |   uint8_t turn_on = BH1750_COMMAND_POWER_ON; | ||||||
|   if (this->write(&turn_on, 1) != i2c::ERROR_OK) { |   if (this->write(&turn_on, 1) != i2c::ERROR_OK) { | ||||||
|     ESP_LOGW(TAG, "Turning on BH1750 failed"); |     ESP_LOGW(TAG, "Power on failed"); | ||||||
|     f(NAN); |     f(NAN); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -60,7 +60,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | |||||||
|     uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111); |     uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111); | ||||||
|     uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111); |     uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111); | ||||||
|     if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { |     if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { | ||||||
|       ESP_LOGW(TAG, "Setting measurement time for BH1750 failed"); |       ESP_LOGW(TAG, "Set measurement time failed"); | ||||||
|       active_mtreg_ = 0; |       active_mtreg_ = 0; | ||||||
|       f(NAN); |       f(NAN); | ||||||
|       return; |       return; | ||||||
| @@ -88,7 +88,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | |||||||
|       return; |       return; | ||||||
|   } |   } | ||||||
|   if (this->write(&cmd, 1) != i2c::ERROR_OK) { |   if (this->write(&cmd, 1) != i2c::ERROR_OK) { | ||||||
|     ESP_LOGW(TAG, "Starting measurement for BH1750 failed"); |     ESP_LOGW(TAG, "Start measurement failed"); | ||||||
|     f(NAN); |     f(NAN); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -99,7 +99,7 @@ void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function< | |||||||
|   this->set_timeout("read", meas_time, [this, mode, mtreg, f]() { |   this->set_timeout("read", meas_time, [this, mode, mtreg, f]() { | ||||||
|     uint16_t raw_value; |     uint16_t raw_value; | ||||||
|     if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) { |     if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) { | ||||||
|       ESP_LOGW(TAG, "Reading BH1750 data failed"); |       ESP_LOGW(TAG, "Read data failed"); | ||||||
|       f(NAN); |       f(NAN); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| @@ -156,7 +156,7 @@ void BH1750Sensor::update() { | |||||||
|         this->publish_state(NAN); |         this->publish_state(NAN); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), val); |       ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val); | ||||||
|       this->status_clear_warning(); |       this->status_clear_warning(); | ||||||
|       this->publish_state(val); |       this->publish_state(val); | ||||||
|     }); |     }); | ||||||
|   | |||||||
| @@ -60,8 +60,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_WINDOW, |     DEVICE_CLASS_WINDOW, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| @@ -148,6 +148,7 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi | |||||||
|  |  | ||||||
| # Filters | # Filters | ||||||
| Filter = binary_sensor_ns.class_("Filter") | Filter = binary_sensor_ns.class_("Filter") | ||||||
|  | TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component) | ||||||
| DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) | DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) | ||||||
| DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) | ||||||
| DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) | ||||||
| @@ -171,6 +172,19 @@ async def invert_filter_to_code(config, filter_id): | |||||||
|     return cg.new_Pvariable(filter_id) |     return cg.new_Pvariable(filter_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_filter( | ||||||
|  |     "timeout", | ||||||
|  |     TimeoutFilter, | ||||||
|  |     cv.templatable(cv.positive_time_period_milliseconds), | ||||||
|  | ) | ||||||
|  | async def timeout_filter_to_code(config, filter_id): | ||||||
|  |     var = cg.new_Pvariable(filter_id) | ||||||
|  |     await cg.register_component(var, {}) | ||||||
|  |     template_ = await cg.templatable(config, [], cg.uint32) | ||||||
|  |     cg.add(var.set_timeout_value(template_)) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
| @register_filter( | @register_filter( | ||||||
|     "delayed_on_off", |     "delayed_on_off", | ||||||
|     DelayedOnOffFilter, |     DelayedOnOffFilter, | ||||||
| @@ -491,6 +505,9 @@ _BINARY_SENSOR_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def binary_sensor_schema( | def binary_sensor_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -521,7 +538,7 @@ BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_binary_sensor_core_(var, config): | async def setup_binary_sensor_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "binary_sensor") | ||||||
|  |  | ||||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: |     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||||
|         cg.add(var.set_device_class(device_class)) |         cg.add(var.set_device_class(device_class)) | ||||||
|   | |||||||
| @@ -25,6 +25,12 @@ void Filter::input(bool value) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void TimeoutFilter::input(bool value) { | ||||||
|  |   this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); }); | ||||||
|  |   // we do not de-dup here otherwise changes from invalid to valid state will not be output | ||||||
|  |   this->output(value); | ||||||
|  | } | ||||||
|  |  | ||||||
| optional<bool> DelayedOnOffFilter::new_value(bool value) { | optional<bool> DelayedOnOffFilter::new_value(bool value) { | ||||||
|   if (value) { |   if (value) { | ||||||
|     this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); }); |     this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); }); | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ class Filter { | |||||||
|  public: |  public: | ||||||
|   virtual optional<bool> new_value(bool value) = 0; |   virtual optional<bool> new_value(bool value) = 0; | ||||||
|  |  | ||||||
|   void input(bool value); |   virtual void input(bool value); | ||||||
|  |  | ||||||
|   void output(bool value); |   void output(bool value); | ||||||
|  |  | ||||||
| @@ -28,6 +28,16 @@ class Filter { | |||||||
|   Deduplicator<bool> dedup_; |   Deduplicator<bool> dedup_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class TimeoutFilter : public Filter, public Component { | ||||||
|  |  public: | ||||||
|  |   optional<bool> new_value(bool value) override { return value; } | ||||||
|  |   void input(bool value) override; | ||||||
|  |   template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   TemplatableValue<uint32_t> timeout_delay_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class DelayedOnOffFilter : public Filter, public Component { | class DelayedOnOffFilter : public Filter, public Component { | ||||||
|  public: |  public: | ||||||
|   optional<bool> new_value(bool value) override; |   optional<bool> new_value(bool value) override; | ||||||
|   | |||||||
| @@ -18,8 +18,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_UPDATE, |     DEVICE_CLASS_UPDATE, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -61,6 +61,9 @@ _BUTTON_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _BUTTON_SCHEMA.add_extra(entity_duplicate_validator("button")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def button_schema( | def button_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -87,7 +90,7 @@ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_button_core_(var, config): | async def setup_button_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "button") | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_PRESS, []): |     for conf in config.get(CONF_ON_PRESS, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -48,8 +48,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -247,6 +247,9 @@ _CLIMATE_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _CLIMATE_SCHEMA.add_extra(entity_duplicate_validator("climate")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def climate_schema( | def climate_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -273,7 +276,7 @@ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_climate_core_(var, config): | async def setup_climate_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "climate") | ||||||
|  |  | ||||||
|     visual = config[CONF_VISUAL] |     visual = config[CONF_VISUAL] | ||||||
|     if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: |     if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: | ||||||
|   | |||||||
| @@ -33,8 +33,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_WINDOW, |     DEVICE_CLASS_WINDOW, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -126,6 +126,9 @@ _COVER_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _COVER_SCHEMA.add_extra(entity_duplicate_validator("cover")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def cover_schema( | def cover_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -154,7 +157,7 @@ COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_cover_core_(var, config): | async def setup_cover_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "cover") | ||||||
|  |  | ||||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: |     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||||
|         cg.add(var.set_device_class(device_class)) |         cg.add(var.set_device_class(device_class)) | ||||||
|   | |||||||
| @@ -22,8 +22,8 @@ from esphome.const import ( | |||||||
|     CONF_YEAR, |     CONF_YEAR, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@rfdarter", "@jesserockz"] | CODEOWNERS = ["@rfdarter", "@jesserockz"] | ||||||
|  |  | ||||||
| @@ -84,6 +84,8 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | |||||||
|     .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) |     .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) | ||||||
| ).add_extra(_validate_time_present) | ).add_extra(_validate_time_present) | ||||||
|  |  | ||||||
|  | _DATETIME_SCHEMA.add_extra(entity_duplicate_validator("datetime")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def date_schema(class_: MockObjClass) -> cv.Schema: | def date_schema(class_: MockObjClass) -> cv.Schema: | ||||||
|     schema = cv.Schema( |     schema = cv.Schema( | ||||||
| @@ -133,7 +135,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema: | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_datetime_core_(var, config): | async def setup_datetime_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "datetime") | ||||||
|  |  | ||||||
|     if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: |     if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: | ||||||
|         mqtt_ = cg.new_Pvariable(mqtt_id, var) |         mqtt_ = cg.new_Pvariable(mqtt_id, var) | ||||||
|   | |||||||
| @@ -455,7 +455,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                     CONF_NAME: "Demo Plain Sensor", |                     CONF_NAME: "Demo Plain Sensor", | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     CONF_NAME: "Demo Temperature Sensor", |                     CONF_NAME: "Demo Temperature Sensor 1", | ||||||
|                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, |                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, | ||||||
|                     CONF_ICON: ICON_THERMOMETER, |                     CONF_ICON: ICON_THERMOMETER, | ||||||
|                     CONF_ACCURACY_DECIMALS: 1, |                     CONF_ACCURACY_DECIMALS: 1, | ||||||
| @@ -463,7 +463,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                     CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, |                     CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT, | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     CONF_NAME: "Demo Temperature Sensor", |                     CONF_NAME: "Demo Temperature Sensor 2", | ||||||
|                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, |                     CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS, | ||||||
|                     CONF_ICON: ICON_THERMOMETER, |                     CONF_ICON: ICON_THERMOMETER, | ||||||
|                     CONF_ACCURACY_DECIMALS: 1, |                     CONF_ACCURACY_DECIMALS: 1, | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import logging | |||||||
| import os | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
| from esphome import git | from esphome import yaml_util | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
| @@ -23,7 +23,6 @@ from esphome.const import ( | |||||||
|     CONF_REFRESH, |     CONF_REFRESH, | ||||||
|     CONF_SOURCE, |     CONF_SOURCE, | ||||||
|     CONF_TYPE, |     CONF_TYPE, | ||||||
|     CONF_URL, |  | ||||||
|     CONF_VARIANT, |     CONF_VARIANT, | ||||||
|     CONF_VERSION, |     CONF_VERSION, | ||||||
|     KEY_CORE, |     KEY_CORE, | ||||||
| @@ -32,14 +31,13 @@ from esphome.const import ( | |||||||
|     KEY_TARGET_FRAMEWORK, |     KEY_TARGET_FRAMEWORK, | ||||||
|     KEY_TARGET_PLATFORM, |     KEY_TARGET_PLATFORM, | ||||||
|     PLATFORM_ESP32, |     PLATFORM_ESP32, | ||||||
|     TYPE_GIT, |  | ||||||
|     TYPE_LOCAL, |  | ||||||
|     __version__, |     __version__, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, HexInt, TimePeriod | from esphome.core import CORE, HexInt, TimePeriod | ||||||
| from esphome.cpp_generator import RawExpression | from esphome.cpp_generator import RawExpression | ||||||
| import esphome.final_validate as fv | import esphome.final_validate as fv | ||||||
| from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed | from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed | ||||||
|  | from esphome.types import ConfigType | ||||||
|  |  | ||||||
| from .boards import BOARDS | from .boards import BOARDS | ||||||
| from .const import (  # noqa | from .const import (  # noqa | ||||||
| @@ -49,10 +47,8 @@ from .const import (  # noqa | |||||||
|     KEY_EXTRA_BUILD_FILES, |     KEY_EXTRA_BUILD_FILES, | ||||||
|     KEY_PATH, |     KEY_PATH, | ||||||
|     KEY_REF, |     KEY_REF, | ||||||
|     KEY_REFRESH, |  | ||||||
|     KEY_REPO, |     KEY_REPO, | ||||||
|     KEY_SDKCONFIG_OPTIONS, |     KEY_SDKCONFIG_OPTIONS, | ||||||
|     KEY_SUBMODULES, |  | ||||||
|     KEY_VARIANT, |     KEY_VARIANT, | ||||||
|     VARIANT_ESP32, |     VARIANT_ESP32, | ||||||
|     VARIANT_ESP32C2, |     VARIANT_ESP32C2, | ||||||
| @@ -235,7 +231,7 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): | |||||||
| def add_idf_component( | def add_idf_component( | ||||||
|     *, |     *, | ||||||
|     name: str, |     name: str, | ||||||
|     repo: str, |     repo: str = None, | ||||||
|     ref: str = None, |     ref: str = None, | ||||||
|     path: str = None, |     path: str = None, | ||||||
|     refresh: TimePeriod = None, |     refresh: TimePeriod = None, | ||||||
| @@ -245,30 +241,27 @@ def add_idf_component( | |||||||
|     """Add an esp-idf component to the project.""" |     """Add an esp-idf component to the project.""" | ||||||
|     if not CORE.using_esp_idf: |     if not CORE.using_esp_idf: | ||||||
|         raise ValueError("Not an esp-idf project") |         raise ValueError("Not an esp-idf project") | ||||||
|     if components is None: |     if not repo and not ref and not path: | ||||||
|         components = [] |         raise ValueError("Requires at least one of repo, ref or path") | ||||||
|     if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]: |     if refresh or submodules or components: | ||||||
|  |         _LOGGER.warning( | ||||||
|  |             "The refresh, components and submodules parameters in add_idf_component() are " | ||||||
|  |             "deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report " | ||||||
|  |             "an issue to the external_component author and ask them to update it." | ||||||
|  |         ) | ||||||
|  |     if components: | ||||||
|  |         for comp in components: | ||||||
|  |             CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = { | ||||||
|  |                 KEY_REPO: repo, | ||||||
|  |                 KEY_REF: ref, | ||||||
|  |                 KEY_PATH: f"{path}/{comp}" if path else comp, | ||||||
|  |             } | ||||||
|  |     else: | ||||||
|         CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = { |         CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = { | ||||||
|             KEY_REPO: repo, |             KEY_REPO: repo, | ||||||
|             KEY_REF: ref, |             KEY_REF: ref, | ||||||
|             KEY_PATH: path, |             KEY_PATH: path, | ||||||
|             KEY_REFRESH: refresh, |  | ||||||
|             KEY_COMPONENTS: components, |  | ||||||
|             KEY_SUBMODULES: submodules, |  | ||||||
|         } |         } | ||||||
|     else: |  | ||||||
|         component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name] |  | ||||||
|         if components is not None: |  | ||||||
|             component_config[KEY_COMPONENTS] = list( |  | ||||||
|                 set(component_config[KEY_COMPONENTS] + components) |  | ||||||
|             ) |  | ||||||
|         if submodules is not None: |  | ||||||
|             if component_config[KEY_SUBMODULES] is None: |  | ||||||
|                 component_config[KEY_SUBMODULES] = submodules |  | ||||||
|             else: |  | ||||||
|                 component_config[KEY_SUBMODULES] = list( |  | ||||||
|                     set(component_config[KEY_SUBMODULES] + submodules) |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def add_extra_script(stage: str, filename: str, path: str): | def add_extra_script(stage: str, filename: str, path: str): | ||||||
| @@ -575,6 +568,17 @@ CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server" | |||||||
| CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" | CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" | ||||||
| CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface" | CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_idf_component(config: ConfigType) -> ConfigType: | ||||||
|  |     """Validate IDF component config and warn about deprecated options.""" | ||||||
|  |     if CONF_REFRESH in config: | ||||||
|  |         _LOGGER.warning( | ||||||
|  |             "The 'refresh' option for IDF components is deprecated and has no effect. " | ||||||
|  |             "It will be removed in ESPHome 2026.1. Please remove it from your configuration." | ||||||
|  |         ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
| @@ -606,7 +610,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | |||||||
|                         CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False |                         CONF_ENABLE_LWIP_DHCP_SERVER, "wifi", default=False | ||||||
|                     ): cv.boolean, |                     ): cv.boolean, | ||||||
|                     cv.Optional( |                     cv.Optional( | ||||||
|                         CONF_ENABLE_LWIP_MDNS_QUERIES, default=False |                         CONF_ENABLE_LWIP_MDNS_QUERIES, default=True | ||||||
|                     ): cv.boolean, |                     ): cv.boolean, | ||||||
|                     cv.Optional( |                     cv.Optional( | ||||||
|                         CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False |                         CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False | ||||||
| @@ -614,15 +618,19 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | |||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( |             cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( | ||||||
|                 cv.Schema( |                 cv.All( | ||||||
|                     { |                     cv.Schema( | ||||||
|                         cv.Required(CONF_NAME): cv.string_strict, |                         { | ||||||
|                         cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA, |                             cv.Required(CONF_NAME): cv.string_strict, | ||||||
|                         cv.Optional(CONF_PATH): cv.string, |                             cv.Optional(CONF_SOURCE): cv.git_ref, | ||||||
|                         cv.Optional(CONF_REFRESH, default="1d"): cv.All( |                             cv.Optional(CONF_REF): cv.string, | ||||||
|                             cv.string, cv.source_refresh |                             cv.Optional(CONF_PATH): cv.string, | ||||||
|                         ), |                             cv.Optional(CONF_REFRESH): cv.All( | ||||||
|                     } |                                 cv.string, cv.source_refresh | ||||||
|  |                             ), | ||||||
|  |                         } | ||||||
|  |                     ), | ||||||
|  |                     _validate_idf_component, | ||||||
|                 ) |                 ) | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
| @@ -762,7 +770,7 @@ async def to_code(config): | |||||||
|             and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] |             and not advanced[CONF_ENABLE_LWIP_DHCP_SERVER] | ||||||
|         ): |         ): | ||||||
|             add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) |             add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False) | ||||||
|         if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, False): |         if not advanced.get(CONF_ENABLE_LWIP_MDNS_QUERIES, True): | ||||||
|             add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) |             add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False) | ||||||
|         if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): |         if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): | ||||||
|             add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) |             add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) | ||||||
| @@ -814,18 +822,12 @@ async def to_code(config): | |||||||
|             add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) |             add_idf_sdkconfig_option(name, RawSdkconfigValue(value)) | ||||||
|  |  | ||||||
|         for component in conf[CONF_COMPONENTS]: |         for component in conf[CONF_COMPONENTS]: | ||||||
|             source = component[CONF_SOURCE] |             add_idf_component( | ||||||
|             if source[CONF_TYPE] == TYPE_GIT: |                 name=component[CONF_NAME], | ||||||
|                 add_idf_component( |                 repo=component.get(CONF_SOURCE), | ||||||
|                     name=component[CONF_NAME], |                 ref=component.get(CONF_REF), | ||||||
|                     repo=source[CONF_URL], |                 path=component.get(CONF_PATH), | ||||||
|                     ref=source.get(CONF_REF), |             ) | ||||||
|                     path=component.get(CONF_PATH), |  | ||||||
|                     refresh=component[CONF_REFRESH], |  | ||||||
|                 ) |  | ||||||
|             elif source[CONF_TYPE] == TYPE_LOCAL: |  | ||||||
|                 _LOGGER.warning("Local components are not implemented yet.") |  | ||||||
|  |  | ||||||
|     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: |     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||||
|         cg.add_platformio_option("framework", "arduino") |         cg.add_platformio_option("framework", "arduino") | ||||||
|         cg.add_build_flag("-DUSE_ARDUINO") |         cg.add_build_flag("-DUSE_ARDUINO") | ||||||
| @@ -924,6 +926,26 @@ def _write_sdkconfig(): | |||||||
|         write_file_if_changed(sdk_path, contents) |         write_file_if_changed(sdk_path, contents) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _write_idf_component_yml(): | ||||||
|  |     yml_path = Path(CORE.relative_build_path("src/idf_component.yml")) | ||||||
|  |     if CORE.data[KEY_ESP32][KEY_COMPONENTS]: | ||||||
|  |         components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS] | ||||||
|  |         dependencies = {} | ||||||
|  |         for name, component in components.items(): | ||||||
|  |             dependency = {} | ||||||
|  |             if component[KEY_REF]: | ||||||
|  |                 dependency["version"] = component[KEY_REF] | ||||||
|  |             if component[KEY_REPO]: | ||||||
|  |                 dependency["git"] = component[KEY_REPO] | ||||||
|  |             if component[KEY_PATH]: | ||||||
|  |                 dependency["path"] = component[KEY_PATH] | ||||||
|  |             dependencies[name] = dependency | ||||||
|  |         contents = yaml_util.dump({"dependencies": dependencies}) | ||||||
|  |     else: | ||||||
|  |         contents = "" | ||||||
|  |     write_file_if_changed(yml_path, contents) | ||||||
|  |  | ||||||
|  |  | ||||||
| # Called by writer.py | # Called by writer.py | ||||||
| def copy_files(): | def copy_files(): | ||||||
|     if CORE.using_arduino: |     if CORE.using_arduino: | ||||||
| @@ -936,6 +958,7 @@ def copy_files(): | |||||||
|             ) |             ) | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|         _write_sdkconfig() |         _write_sdkconfig() | ||||||
|  |         _write_idf_component_yml() | ||||||
|         if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: |         if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]: | ||||||
|             write_file_if_changed( |             write_file_if_changed( | ||||||
|                 CORE.relative_build_path("partitions.csv"), |                 CORE.relative_build_path("partitions.csv"), | ||||||
| @@ -952,55 +975,6 @@ def copy_files(): | |||||||
|             __version__, |             __version__, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         import shutil |  | ||||||
|  |  | ||||||
|         shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True) |  | ||||||
|  |  | ||||||
|         if CORE.data[KEY_ESP32][KEY_COMPONENTS]: |  | ||||||
|             components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS] |  | ||||||
|  |  | ||||||
|             for name, component in components.items(): |  | ||||||
|                 repo_dir, _ = git.clone_or_update( |  | ||||||
|                     url=component[KEY_REPO], |  | ||||||
|                     ref=component[KEY_REF], |  | ||||||
|                     refresh=component[KEY_REFRESH], |  | ||||||
|                     domain="idf_components", |  | ||||||
|                     submodules=component[KEY_SUBMODULES], |  | ||||||
|                 ) |  | ||||||
|                 mkdir_p(CORE.relative_build_path("components")) |  | ||||||
|                 component_dir = repo_dir |  | ||||||
|                 if component[KEY_PATH] is not None: |  | ||||||
|                     component_dir = component_dir / component[KEY_PATH] |  | ||||||
|  |  | ||||||
|                 if component[KEY_COMPONENTS] == ["*"]: |  | ||||||
|                     shutil.copytree( |  | ||||||
|                         component_dir, |  | ||||||
|                         CORE.relative_build_path("components"), |  | ||||||
|                         dirs_exist_ok=True, |  | ||||||
|                         ignore=shutil.ignore_patterns(".git*"), |  | ||||||
|                         symlinks=True, |  | ||||||
|                         ignore_dangling_symlinks=True, |  | ||||||
|                     ) |  | ||||||
|                 elif len(component[KEY_COMPONENTS]) > 0: |  | ||||||
|                     for comp in component[KEY_COMPONENTS]: |  | ||||||
|                         shutil.copytree( |  | ||||||
|                             component_dir / comp, |  | ||||||
|                             CORE.relative_build_path(f"components/{comp}"), |  | ||||||
|                             dirs_exist_ok=True, |  | ||||||
|                             ignore=shutil.ignore_patterns(".git*"), |  | ||||||
|                             symlinks=True, |  | ||||||
|                             ignore_dangling_symlinks=True, |  | ||||||
|                         ) |  | ||||||
|                 else: |  | ||||||
|                     shutil.copytree( |  | ||||||
|                         component_dir, |  | ||||||
|                         CORE.relative_build_path(f"components/{name}"), |  | ||||||
|                         dirs_exist_ok=True, |  | ||||||
|                         ignore=shutil.ignore_patterns(".git*"), |  | ||||||
|                         symlinks=True, |  | ||||||
|                         ignore_dangling_symlinks=True, |  | ||||||
|                     ) |  | ||||||
|  |  | ||||||
|     for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items(): |     for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items(): | ||||||
|         if file[KEY_PATH].startswith("http"): |         if file[KEY_PATH].startswith("http"): | ||||||
|             import requests |             import requests | ||||||
|   | |||||||
| @@ -29,9 +29,9 @@ class ESP32InternalGPIOPin : public InternalGPIOPin { | |||||||
|   void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; |   void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; | ||||||
|  |  | ||||||
|   gpio_num_t pin_; |   gpio_num_t pin_; | ||||||
|   bool inverted_; |  | ||||||
|   gpio_drive_cap_t drive_strength_; |   gpio_drive_cap_t drive_strength_; | ||||||
|   gpio::Flags flags_; |   gpio::Flags flags_; | ||||||
|  |   bool inverted_; | ||||||
|   // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) |   // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   static bool isr_service_installed; |   static bool isr_service_installed; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| #include "ble_event_pool.h" | #include "ble_event_pool.h" | ||||||
|  |  | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #include <esp_bt.h> | #include <esp_bt.h> | ||||||
| @@ -516,13 +517,12 @@ void ESP32BLE::dump_config() { | |||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     ESP_LOGCONFIG(TAG, |     ESP_LOGCONFIG(TAG, | ||||||
|                   "ESP32 BLE:\n" |                   "BLE:\n" | ||||||
|                   "  MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n" |                   "  MAC address: %s\n" | ||||||
|                   "  IO Capability: %s", |                   "  IO Capability: %s", | ||||||
|                   mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5], |                   format_mac_address_pretty(mac_address).c_str(), io_capability_s); | ||||||
|                   io_capability_s); |  | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); |     ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -496,17 +496,17 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { | |||||||
|       if (length > 2) { |       if (length > 2) { | ||||||
|         return (float) encode_uint16(value[1], value[2]); |         return (float) encode_uint16(value[1], value[2]); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|     case 0x7:  // uint24. |     case 0x7:  // uint24. | ||||||
|       if (length > 3) { |       if (length > 3) { | ||||||
|         return (float) encode_uint24(value[1], value[2], value[3]); |         return (float) encode_uint24(value[1], value[2], value[3]); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|     case 0x8:  // uint32. |     case 0x8:  // uint32. | ||||||
|       if (length > 4) { |       if (length > 4) { | ||||||
|         return (float) encode_uint32(value[1], value[2], value[3], value[4]); |         return (float) encode_uint32(value[1], value[2], value[3], value[4]); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|     case 0xC:  // int8. |     case 0xC:  // int8. | ||||||
|       return (float) ((int8_t) value[1]); |       return (float) ((int8_t) value[1]); | ||||||
|     case 0xD:  // int12. |     case 0xD:  // int12. | ||||||
| @@ -514,12 +514,12 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) { | |||||||
|       if (length > 2) { |       if (length > 2) { | ||||||
|         return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]); |         return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|     case 0xF:  // int24. |     case 0xF:  // int24. | ||||||
|       if (length > 3) { |       if (length > 3) { | ||||||
|         return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3])); |         return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3])); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|     case 0x10:  // int32. |     case 0x10:  // int32. | ||||||
|       if (length > 4) { |       if (length > 4) { | ||||||
|         return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) + |         return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) + | ||||||
|   | |||||||
| @@ -522,6 +522,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData | |||||||
| } | } | ||||||
|  |  | ||||||
| void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { | void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { | ||||||
|  |   this->scan_result_ = &scan_result; | ||||||
|   for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++) |   for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++) | ||||||
|     this->address_[i] = scan_result.bda[i]; |     this->address_[i] = scan_result.bda[i]; | ||||||
|   this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type); |   this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type); | ||||||
|   | |||||||
| @@ -85,6 +85,9 @@ class ESPBTDevice { | |||||||
|  |  | ||||||
|   const std::vector<ServiceData> &get_service_datas() const { return service_datas_; } |   const std::vector<ServiceData> &get_service_datas() const { return service_datas_; } | ||||||
|  |  | ||||||
|  |   // Exposed through a function for use in lambdas | ||||||
|  |   const BLEScanResult &get_scan_result() const { return *scan_result_; } | ||||||
|  |  | ||||||
|   bool resolve_irk(const uint8_t *irk) const; |   bool resolve_irk(const uint8_t *irk) const; | ||||||
|  |  | ||||||
|   optional<ESPBLEiBeacon> get_ibeacon() const { |   optional<ESPBLEiBeacon> get_ibeacon() const { | ||||||
| @@ -111,6 +114,7 @@ class ESPBTDevice { | |||||||
|   std::vector<ESPBTUUID> service_uuids_{}; |   std::vector<ESPBTUUID> service_uuids_{}; | ||||||
|   std::vector<ServiceData> manufacturer_datas_{}; |   std::vector<ServiceData> manufacturer_datas_{}; | ||||||
|   std::vector<ServiceData> service_datas_{}; |   std::vector<ServiceData> service_datas_{}; | ||||||
|  |   const BLEScanResult *scan_result_{nullptr}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ESP32BLETracker; | class ESP32BLETracker; | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ from esphome.const import ( | |||||||
|     CONF_VSYNC_PIN, |     CONF_VSYNC_PIN, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
| from esphome.cpp_helpers import setup_entity | from esphome.core.entity_helpers import setup_entity | ||||||
|  |  | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
|  |  | ||||||
| @@ -284,7 +284,7 @@ SETTERS = { | |||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "camera") | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     for key, setter in SETTERS.items(): |     for key, setter in SETTERS.items(): | ||||||
| @@ -310,11 +310,7 @@ async def to_code(config): | |||||||
|     cg.add_define("USE_ESP32_CAMERA") |     cg.add_define("USE_ESP32_CAMERA") | ||||||
|  |  | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|         add_idf_component( |         add_idf_component(name="espressif/esp32-camera", ref="2.0.15") | ||||||
|             name="esp32-camera", |  | ||||||
|             repo="https://github.com/espressif/esp32-camera.git", |  | ||||||
|             ref="v2.0.15", |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_STREAM_START, []): |     for conf in config.get(CONF_ON_STREAM_START, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/esp32_hall/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/esp32_hall/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										5
									
								
								esphome/components/esp32_hall/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								esphome/components/esp32_hall/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import esphome.config_validation as cv | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.invalid( | ||||||
|  |     "The esp32_hall component has been removed as of ESPHome 2025.7.0. See https://github.com/esphome/esphome/pull/9117 for details." | ||||||
|  | ) | ||||||
							
								
								
									
										101
									
								
								esphome/components/esp32_hosted/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								esphome/components/esp32_hosted/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | import os | ||||||
|  |  | ||||||
|  | from esphome import pins | ||||||
|  | from esphome.components import esp32 | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_CLK_PIN, | ||||||
|  |     CONF_RESET_PIN, | ||||||
|  |     CONF_VARIANT, | ||||||
|  |     KEY_CORE, | ||||||
|  |     KEY_FRAMEWORK_VERSION, | ||||||
|  | ) | ||||||
|  | from esphome.core import CORE | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@swoboda1337"] | ||||||
|  |  | ||||||
|  | CONF_ACTIVE_HIGH = "active_high" | ||||||
|  | CONF_CMD_PIN = "cmd_pin" | ||||||
|  | CONF_D0_PIN = "d0_pin" | ||||||
|  | CONF_D1_PIN = "d1_pin" | ||||||
|  | CONF_D2_PIN = "d2_pin" | ||||||
|  | CONF_D3_PIN = "d3_pin" | ||||||
|  | CONF_SLOT = "slot" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_VARIANT): cv.one_of(*esp32.VARIANTS, upper=True), | ||||||
|  |             cv.Required(CONF_ACTIVE_HIGH): cv.boolean, | ||||||
|  |             cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_CMD_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_D0_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_D1_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_D2_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_D3_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Optional(CONF_SLOT, default=1): cv.int_range(min=0, max=1), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     if config[CONF_ACTIVE_HIGH]: | ||||||
|  |         esp32.add_idf_sdkconfig_option( | ||||||
|  |             "CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_HIGH", | ||||||
|  |             True, | ||||||
|  |         ) | ||||||
|  |     else: | ||||||
|  |         esp32.add_idf_sdkconfig_option( | ||||||
|  |             "CONFIG_ESP_HOSTED_SDIO_RESET_ACTIVE_LOW", | ||||||
|  |             True, | ||||||
|  |         ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         "CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE",  # NOLINT | ||||||
|  |         config[CONF_RESET_PIN], | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_SLAVE_IDF_TARGET_{config[CONF_VARIANT]}",  # NOLINT | ||||||
|  |         True, | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_ESP_HOSTED_SDIO_SLOT_{config[CONF_SLOT]}", | ||||||
|  |         True, | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_{config[CONF_SLOT]}", | ||||||
|  |         config[CONF_CLK_PIN], | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_{config[CONF_SLOT]}", | ||||||
|  |         config[CONF_CMD_PIN], | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_{config[CONF_SLOT]}", | ||||||
|  |         config[CONF_D0_PIN], | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_{config[CONF_SLOT]}", | ||||||
|  |         config[CONF_D1_PIN], | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_{config[CONF_SLOT]}", | ||||||
|  |         config[CONF_D2_PIN], | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option( | ||||||
|  |         f"CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_{config[CONF_SLOT]}", | ||||||
|  |         config[CONF_D3_PIN], | ||||||
|  |     ) | ||||||
|  |     esp32.add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_CUSTOM_SDIO_PINS", True) | ||||||
|  |  | ||||||
|  |     framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] | ||||||
|  |     os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}" | ||||||
|  |     esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.10.2") | ||||||
|  |     esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0") | ||||||
|  |     esp32.add_idf_component(name="espressif/esp_hosted", ref="2.0.11") | ||||||
|  |     esp32.add_extra_script( | ||||||
|  |         "post", | ||||||
|  |         "esp32_hosted.py", | ||||||
|  |         os.path.join(os.path.dirname(__file__), "esp32_hosted.py.script"), | ||||||
|  |     ) | ||||||
							
								
								
									
										12
									
								
								esphome/components/esp32_hosted/esp32_hosted.py.script
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/esp32_hosted/esp32_hosted.py.script
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # pylint: disable=E0602 | ||||||
|  | Import("env")  # noqa | ||||||
|  |  | ||||||
|  | # Workaround whole archive issue | ||||||
|  | if "__LIB_DEPS" in env and "libespressif__esp_hosted.a" in env["__LIB_DEPS"]: | ||||||
|  |     env.Append( | ||||||
|  |         LINKFLAGS=[ | ||||||
|  |             "-Wl,--whole-archive", | ||||||
|  |             env["BUILD_DIR"] + "/esp-idf/espressif__esp_hosted/libespressif__esp_hosted.a", | ||||||
|  |             "-Wl,--no-whole-archive", | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
| @@ -15,7 +15,7 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ethernet { | namespace ethernet { | ||||||
|  |  | ||||||
| enum EthernetType { | enum EthernetType : uint8_t { | ||||||
|   ETHERNET_TYPE_UNKNOWN = 0, |   ETHERNET_TYPE_UNKNOWN = 0, | ||||||
|   ETHERNET_TYPE_LAN8720, |   ETHERNET_TYPE_LAN8720, | ||||||
|   ETHERNET_TYPE_RTL8201, |   ETHERNET_TYPE_RTL8201, | ||||||
| @@ -42,7 +42,7 @@ struct PHYRegister { | |||||||
|   uint32_t page; |   uint32_t page; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class EthernetComponentState { | enum class EthernetComponentState : uint8_t { | ||||||
|   STOPPED, |   STOPPED, | ||||||
|   CONNECTING, |   CONNECTING, | ||||||
|   CONNECTED, |   CONNECTED, | ||||||
| @@ -119,25 +119,31 @@ class EthernetComponent : public Component { | |||||||
|   uint32_t polling_interval_{0}; |   uint32_t polling_interval_{0}; | ||||||
| #endif | #endif | ||||||
| #else | #else | ||||||
|   uint8_t phy_addr_{0}; |   // Group all 32-bit members first | ||||||
|   int power_pin_{-1}; |   int power_pin_{-1}; | ||||||
|   uint8_t mdc_pin_{23}; |  | ||||||
|   uint8_t mdio_pin_{18}; |  | ||||||
|   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; |   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; | ||||||
|   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; |   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; | ||||||
|   std::vector<PHYRegister> phy_registers_{}; |   std::vector<PHYRegister> phy_registers_{}; | ||||||
| #endif |  | ||||||
|   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; |  | ||||||
|   optional<ManualIP> manual_ip_{}; |  | ||||||
|  |  | ||||||
|  |   // Group all 8-bit members together | ||||||
|  |   uint8_t phy_addr_{0}; | ||||||
|  |   uint8_t mdc_pin_{23}; | ||||||
|  |   uint8_t mdio_pin_{18}; | ||||||
|  | #endif | ||||||
|  |   optional<ManualIP> manual_ip_{}; | ||||||
|  |   uint32_t connect_begin_; | ||||||
|  |  | ||||||
|  |   // Group all uint8_t types together (enums and bools) | ||||||
|  |   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; | ||||||
|  |   EthernetComponentState state_{EthernetComponentState::STOPPED}; | ||||||
|   bool started_{false}; |   bool started_{false}; | ||||||
|   bool connected_{false}; |   bool connected_{false}; | ||||||
|   bool got_ipv4_address_{false}; |   bool got_ipv4_address_{false}; | ||||||
| #if LWIP_IPV6 | #if LWIP_IPV6 | ||||||
|   uint8_t ipv6_count_{0}; |   uint8_t ipv6_count_{0}; | ||||||
| #endif /* LWIP_IPV6 */ | #endif /* LWIP_IPV6 */ | ||||||
|   EthernetComponentState state_{EthernetComponentState::STOPPED}; |  | ||||||
|   uint32_t connect_begin_; |   // Pointers at the end (naturally aligned) | ||||||
|   esp_netif_t *eth_netif_{nullptr}; |   esp_netif_t *eth_netif_{nullptr}; | ||||||
|   esp_eth_handle_t eth_handle_; |   esp_eth_handle_t eth_handle_; | ||||||
|   esp_eth_phy_t *phy_{nullptr}; |   esp_eth_phy_t *phy_{nullptr}; | ||||||
|   | |||||||
| @@ -18,8 +18,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_MOTION, |     DEVICE_CLASS_MOTION, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@nohat"] | CODEOWNERS = ["@nohat"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -59,6 +59,9 @@ _EVENT_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _EVENT_SCHEMA.add_extra(entity_duplicate_validator("event")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def event_schema( | def event_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -88,7 +91,7 @@ EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_event_core_(var, config, *, event_types: list[str]): | async def setup_event_core_(var, config, *, event_types: list[str]): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "event") | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_EVENT, []): |     for conf in config.get(CONF_ON_EVENT, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
| from esphome.cpp_helpers import setup_entity | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
|  |  | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| @@ -161,6 +161,9 @@ _FAN_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _FAN_SCHEMA.add_extra(entity_duplicate_validator("fan")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def fan_schema( | def fan_schema( | ||||||
|     class_: cg.Pvariable, |     class_: cg.Pvariable, | ||||||
|     *, |     *, | ||||||
| @@ -225,7 +228,7 @@ def validate_preset_modes(value): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_fan_core_(var, config): | async def setup_fan_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "fan") | ||||||
|  |  | ||||||
|     cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) |     cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| from collections.abc import MutableMapping | from collections.abc import MutableMapping | ||||||
| import functools | import functools | ||||||
| import hashlib | import hashlib | ||||||
|  | from itertools import accumulate | ||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| @@ -468,8 +469,9 @@ class EFont: | |||||||
|  |  | ||||||
|  |  | ||||||
| class GlyphInfo: | class GlyphInfo: | ||||||
|     def __init__(self, data_len, advance, offset_x, offset_y, width, height): |     def __init__(self, glyph, data, advance, offset_x, offset_y, width, height): | ||||||
|         self.data_len = data_len |         self.glyph = glyph | ||||||
|  |         self.bitmap_data = data | ||||||
|         self.advance = advance |         self.advance = advance | ||||||
|         self.offset_x = offset_x |         self.offset_x = offset_x | ||||||
|         self.offset_y = offset_y |         self.offset_y = offset_y | ||||||
| @@ -477,6 +479,62 @@ class GlyphInfo: | |||||||
|         self.height = height |         self.height = height | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def glyph_to_glyphinfo(glyph, font, size, bpp): | ||||||
|  |     scale = 256 // (1 << bpp) | ||||||
|  |     if not font.is_scalable: | ||||||
|  |         sizes = [pt_to_px(x.size) for x in font.available_sizes] | ||||||
|  |         if size in sizes: | ||||||
|  |             font.select_size(sizes.index(size)) | ||||||
|  |     else: | ||||||
|  |         font.set_pixel_sizes(size, 0) | ||||||
|  |     flags = FT_LOAD_RENDER | ||||||
|  |     if bpp != 1: | ||||||
|  |         flags |= FT_LOAD_NO_BITMAP | ||||||
|  |     else: | ||||||
|  |         flags |= FT_LOAD_TARGET_MONO | ||||||
|  |     font.load_char(glyph, flags) | ||||||
|  |     width = font.glyph.bitmap.width | ||||||
|  |     height = font.glyph.bitmap.rows | ||||||
|  |     buffer = font.glyph.bitmap.buffer | ||||||
|  |     pitch = font.glyph.bitmap.pitch | ||||||
|  |     glyph_data = [0] * ((height * width * bpp + 7) // 8) | ||||||
|  |     src_mode = font.glyph.bitmap.pixel_mode | ||||||
|  |     pos = 0 | ||||||
|  |     for y in range(height): | ||||||
|  |         for x in range(width): | ||||||
|  |             if src_mode == ft_pixel_mode_mono: | ||||||
|  |                 pixel = ( | ||||||
|  |                     (1 << bpp) - 1 | ||||||
|  |                     if buffer[y * pitch + x // 8] & (1 << (7 - x % 8)) | ||||||
|  |                     else 0 | ||||||
|  |                 ) | ||||||
|  |             else: | ||||||
|  |                 pixel = buffer[y * pitch + x] // scale | ||||||
|  |             for bit_num in range(bpp): | ||||||
|  |                 if pixel & (1 << (bpp - bit_num - 1)): | ||||||
|  |                     glyph_data[pos // 8] |= 0x80 >> (pos % 8) | ||||||
|  |                 pos += 1 | ||||||
|  |     ascender = pt_to_px(font.size.ascender) | ||||||
|  |     if ascender == 0: | ||||||
|  |         if not font.is_scalable: | ||||||
|  |             ascender = size | ||||||
|  |         else: | ||||||
|  |             _LOGGER.error( | ||||||
|  |                 "Unable to determine ascender of font %s %s", | ||||||
|  |                 font.family_name, | ||||||
|  |                 font.style_name, | ||||||
|  |             ) | ||||||
|  |     return GlyphInfo( | ||||||
|  |         glyph, | ||||||
|  |         glyph_data, | ||||||
|  |         pt_to_px(font.glyph.metrics.horiAdvance), | ||||||
|  |         font.glyph.bitmap_left, | ||||||
|  |         ascender - font.glyph.bitmap_top, | ||||||
|  |         width, | ||||||
|  |         height, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     """ |     """ | ||||||
|     Collect all glyph codepoints, construct a map from a codepoint to a font file. |     Collect all glyph codepoints, construct a map from a codepoint to a font file. | ||||||
| @@ -506,98 +564,47 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     codepoints = list(point_set) |     codepoints = list(point_set) | ||||||
|     codepoints.sort(key=functools.cmp_to_key(glyph_comparator)) |     codepoints.sort(key=functools.cmp_to_key(glyph_comparator)) | ||||||
|     glyph_args = {} |  | ||||||
|     data = [] |  | ||||||
|     bpp = config[CONF_BPP] |     bpp = config[CONF_BPP] | ||||||
|     scale = 256 // (1 << bpp) |  | ||||||
|     size = config[CONF_SIZE] |     size = config[CONF_SIZE] | ||||||
|     # create the data array for all glyphs |     # create the data array for all glyphs | ||||||
|     for codepoint in codepoints: |     glyph_args = [ | ||||||
|         font = point_font_map[codepoint] |         glyph_to_glyphinfo(x, point_font_map[x], size, bpp) for x in codepoints | ||||||
|         if not font.is_scalable: |     ] | ||||||
|             sizes = [pt_to_px(x.size) for x in font.available_sizes] |     rhs = [HexInt(x) for x in flatten([x.bitmap_data for x in glyph_args])] | ||||||
|             if size in sizes: |  | ||||||
|                 font.select_size(sizes.index(size)) |  | ||||||
|         else: |  | ||||||
|             font.set_pixel_sizes(size, 0) |  | ||||||
|         flags = FT_LOAD_RENDER |  | ||||||
|         if bpp != 1: |  | ||||||
|             flags |= FT_LOAD_NO_BITMAP |  | ||||||
|         else: |  | ||||||
|             flags |= FT_LOAD_TARGET_MONO |  | ||||||
|         font.load_char(codepoint, flags) |  | ||||||
|         width = font.glyph.bitmap.width |  | ||||||
|         height = font.glyph.bitmap.rows |  | ||||||
|         buffer = font.glyph.bitmap.buffer |  | ||||||
|         pitch = font.glyph.bitmap.pitch |  | ||||||
|         glyph_data = [0] * ((height * width * bpp + 7) // 8) |  | ||||||
|         src_mode = font.glyph.bitmap.pixel_mode |  | ||||||
|         pos = 0 |  | ||||||
|         for y in range(height): |  | ||||||
|             for x in range(width): |  | ||||||
|                 if src_mode == ft_pixel_mode_mono: |  | ||||||
|                     pixel = ( |  | ||||||
|                         (1 << bpp) - 1 |  | ||||||
|                         if buffer[y * pitch + x // 8] & (1 << (7 - x % 8)) |  | ||||||
|                         else 0 |  | ||||||
|                     ) |  | ||||||
|                 else: |  | ||||||
|                     pixel = buffer[y * pitch + x] // scale |  | ||||||
|                 for bit_num in range(bpp): |  | ||||||
|                     if pixel & (1 << (bpp - bit_num - 1)): |  | ||||||
|                         glyph_data[pos // 8] |= 0x80 >> (pos % 8) |  | ||||||
|                     pos += 1 |  | ||||||
|         ascender = pt_to_px(font.size.ascender) |  | ||||||
|         if ascender == 0: |  | ||||||
|             if not font.is_scalable: |  | ||||||
|                 ascender = size |  | ||||||
|             else: |  | ||||||
|                 _LOGGER.error( |  | ||||||
|                     "Unable to determine ascender of font %s", config[CONF_FILE] |  | ||||||
|                 ) |  | ||||||
|         glyph_args[codepoint] = GlyphInfo( |  | ||||||
|             len(data), |  | ||||||
|             pt_to_px(font.glyph.metrics.horiAdvance), |  | ||||||
|             font.glyph.bitmap_left, |  | ||||||
|             ascender - font.glyph.bitmap_top, |  | ||||||
|             width, |  | ||||||
|             height, |  | ||||||
|         ) |  | ||||||
|         data += glyph_data |  | ||||||
|  |  | ||||||
|     rhs = [HexInt(x) for x in data] |  | ||||||
|     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) |     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||||
|  |  | ||||||
|     # Create the glyph table that points to data in the above array. |     # Create the glyph table that points to data in the above array. | ||||||
|     glyph_initializer = [] |     glyph_initializer = [ | ||||||
|     for codepoint in codepoints: |         cg.StructInitializer( | ||||||
|         glyph_initializer.append( |             GlyphData, | ||||||
|             cg.StructInitializer( |             ( | ||||||
|                 GlyphData, |                 "a_char", | ||||||
|                 ( |                 cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"), | ||||||
|                     "a_char", |             ), | ||||||
|                     cg.RawExpression( |             ( | ||||||
|                         f"(const uint8_t *){cpp_string_escape(codepoint)}" |                 "data", | ||||||
|                     ), |                 cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"), | ||||||
|                 ), |             ), | ||||||
|                 ( |             ("advance", x.advance), | ||||||
|                     "data", |             ("offset_x", x.offset_x), | ||||||
|                     cg.RawExpression( |             ("offset_y", x.offset_y), | ||||||
|                         f"{str(prog_arr)} + {str(glyph_args[codepoint].data_len)}" |             ("width", x.width), | ||||||
|                     ), |             ("height", x.height), | ||||||
|                 ), |  | ||||||
|                 ("advance", glyph_args[codepoint].advance), |  | ||||||
|                 ("offset_x", glyph_args[codepoint].offset_x), |  | ||||||
|                 ("offset_y", glyph_args[codepoint].offset_y), |  | ||||||
|                 ("width", glyph_args[codepoint].width), |  | ||||||
|                 ("height", glyph_args[codepoint].height), |  | ||||||
|             ) |  | ||||||
|         ) |         ) | ||||||
|  |         for (x, y) in zip( | ||||||
|  |             glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args])) | ||||||
|  |         ) | ||||||
|  |     ] | ||||||
|  |  | ||||||
|     glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) |     glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer) | ||||||
|  |  | ||||||
|     font_height = pt_to_px(base_font.size.height) |     font_height = pt_to_px(base_font.size.height) | ||||||
|     ascender = pt_to_px(base_font.size.ascender) |     ascender = pt_to_px(base_font.size.ascender) | ||||||
|  |     descender = abs(pt_to_px(base_font.size.descender)) | ||||||
|  |     g = glyph_to_glyphinfo("x", base_font, size, bpp) | ||||||
|  |     xheight = g.height if len(g.bitmap_data) > 1 else 0 | ||||||
|  |     g = glyph_to_glyphinfo("X", base_font, size, bpp) | ||||||
|  |     capheight = g.height if len(g.bitmap_data) > 1 else 0 | ||||||
|     if font_height == 0: |     if font_height == 0: | ||||||
|         if not base_font.is_scalable: |         if not base_font.is_scalable: | ||||||
|             font_height = size |             font_height = size | ||||||
| @@ -610,5 +617,8 @@ async def to_code(config): | |||||||
|         len(glyph_initializer), |         len(glyph_initializer), | ||||||
|         ascender, |         ascender, | ||||||
|         font_height, |         font_height, | ||||||
|  |         descender, | ||||||
|  |         xheight, | ||||||
|  |         capheight, | ||||||
|         bpp, |         bpp, | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -45,8 +45,15 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { | |||||||
|   *height = this->glyph_data_->height; |   *height = this->glyph_data_->height; | ||||||
| } | } | ||||||
|  |  | ||||||
| Font::Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp) | Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, | ||||||
|     : baseline_(baseline), height_(height), bpp_(bpp) { |            uint8_t bpp) | ||||||
|  |     : baseline_(baseline), | ||||||
|  |       height_(height), | ||||||
|  |       descender_(descender), | ||||||
|  |       linegap_(height - baseline - descender), | ||||||
|  |       xheight_(xheight), | ||||||
|  |       capheight_(capheight), | ||||||
|  |       bpp_(bpp) { | ||||||
|   glyphs_.reserve(data_nr); |   glyphs_.reserve(data_nr); | ||||||
|   for (int i = 0; i < data_nr; ++i) |   for (int i = 0; i < data_nr; ++i) | ||||||
|     glyphs_.emplace_back(&data[i]); |     glyphs_.emplace_back(&data[i]); | ||||||
|   | |||||||
| @@ -50,11 +50,17 @@ class Font | |||||||
|  public: |  public: | ||||||
|   /** Construct the font with the given glyphs. |   /** Construct the font with the given glyphs. | ||||||
|    * |    * | ||||||
|    * @param glyphs A vector of glyphs, must be sorted lexicographically. |    * @param data A vector of glyphs, must be sorted lexicographically. | ||||||
|  |    * @param data_nr The number of glyphs in data. | ||||||
|    * @param baseline The y-offset from the top of the text to the baseline. |    * @param baseline The y-offset from the top of the text to the baseline. | ||||||
|    * @param bottom The y-offset from the top of the text to the bottom (i.e. height). |    * @param height The y-offset from the top of the text to the bottom. | ||||||
|  |    * @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p). | ||||||
|  |    * @param xheight The height of lowercase letters, usually measured at the "x" glyph. | ||||||
|  |    * @param capheight The height of capital letters, usually measured at the "X" glyph. | ||||||
|  |    * @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps. | ||||||
|    */ |    */ | ||||||
|   Font(const GlyphData *data, int data_nr, int baseline, int height, uint8_t bpp = 1); |   Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, | ||||||
|  |        uint8_t bpp = 1); | ||||||
|  |  | ||||||
|   int match_next_glyph(const uint8_t *str, int *match_length); |   int match_next_glyph(const uint8_t *str, int *match_length); | ||||||
|  |  | ||||||
| @@ -65,6 +71,11 @@ class Font | |||||||
| #endif | #endif | ||||||
|   inline int get_baseline() { return this->baseline_; } |   inline int get_baseline() { return this->baseline_; } | ||||||
|   inline int get_height() { return this->height_; } |   inline int get_height() { return this->height_; } | ||||||
|  |   inline int get_ascender() { return this->baseline_; } | ||||||
|  |   inline int get_descender() { return this->descender_; } | ||||||
|  |   inline int get_linegap() { return this->linegap_; } | ||||||
|  |   inline int get_xheight() { return this->xheight_; } | ||||||
|  |   inline int get_capheight() { return this->capheight_; } | ||||||
|   inline int get_bpp() { return this->bpp_; } |   inline int get_bpp() { return this->bpp_; } | ||||||
|  |  | ||||||
|   const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } |   const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } | ||||||
| @@ -73,6 +84,10 @@ class Font | |||||||
|   std::vector<Glyph, RAMAllocator<Glyph>> glyphs_; |   std::vector<Glyph, RAMAllocator<Glyph>> glyphs_; | ||||||
|   int baseline_; |   int baseline_; | ||||||
|   int height_; |   int height_; | ||||||
|  |   int descender_; | ||||||
|  |   int linegap_; | ||||||
|  |   int xheight_; | ||||||
|  |   int capheight_; | ||||||
|   uint8_t bpp_;  // bits per pixel |   uint8_t bpp_;  // bits per pixel | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,6 +41,6 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     cg.add_build_flag("-DUSE_HOST") |     cg.add_build_flag("-DUSE_HOST") | ||||||
|     cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts) |     cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts) | ||||||
|     cg.add_build_flag("-std=c++17") |     cg.add_build_flag("-std=gnu++17") | ||||||
|     cg.add_define("ESPHOME_BOARD", "host") |     cg.add_define("ESPHOME_BOARD", "host") | ||||||
|     cg.add_platformio_option("platform", "platformio/native") |     cg.add_platformio_option("platform", "platformio/native") | ||||||
|   | |||||||
| @@ -8,6 +8,9 @@ | |||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  |  | ||||||
|  | #define CHECK_BIT(var, pos) (((var) >> (pos)) & 1) | ||||||
| #define highbyte(val) (uint8_t)((val) >> 8) | #define highbyte(val) (uint8_t)((val) >> 8) | ||||||
| #define lowbyte(val) (uint8_t)((val) &0xff) | #define lowbyte(val) (uint8_t)((val) &0xff) | ||||||
|  |  | ||||||
| @@ -15,8 +18,162 @@ namespace esphome { | |||||||
| namespace ld2410 { | namespace ld2410 { | ||||||
|  |  | ||||||
| static const char *const TAG = "ld2410"; | static const char *const TAG = "ld2410"; | ||||||
|  | static const char *const NO_MAC = "08:05:04:03:02:01"; | ||||||
|  | static const char *const UNKNOWN_MAC = "unknown"; | ||||||
|  | static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; | ||||||
|  |  | ||||||
| LD2410Component::LD2410Component() {} | enum BaudRateStructure : uint8_t { | ||||||
|  |   BAUD_RATE_9600 = 1, | ||||||
|  |   BAUD_RATE_19200 = 2, | ||||||
|  |   BAUD_RATE_38400 = 3, | ||||||
|  |   BAUD_RATE_57600 = 4, | ||||||
|  |   BAUD_RATE_115200 = 5, | ||||||
|  |   BAUD_RATE_230400 = 6, | ||||||
|  |   BAUD_RATE_256000 = 7, | ||||||
|  |   BAUD_RATE_460800 = 8, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum DistanceResolutionStructure : uint8_t { | ||||||
|  |   DISTANCE_RESOLUTION_0_2 = 0x01, | ||||||
|  |   DISTANCE_RESOLUTION_0_75 = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum LightFunctionStructure : uint8_t { | ||||||
|  |   LIGHT_FUNCTION_OFF = 0x00, | ||||||
|  |   LIGHT_FUNCTION_BELOW = 0x01, | ||||||
|  |   LIGHT_FUNCTION_ABOVE = 0x02, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum OutPinLevelStructure : uint8_t { | ||||||
|  |   OUT_PIN_LEVEL_LOW = 0x00, | ||||||
|  |   OUT_PIN_LEVEL_HIGH = 0x01, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum PeriodicDataStructure : uint8_t { | ||||||
|  |   DATA_TYPES = 6, | ||||||
|  |   TARGET_STATES = 8, | ||||||
|  |   MOVING_TARGET_LOW = 9, | ||||||
|  |   MOVING_TARGET_HIGH = 10, | ||||||
|  |   MOVING_ENERGY = 11, | ||||||
|  |   STILL_TARGET_LOW = 12, | ||||||
|  |   STILL_TARGET_HIGH = 13, | ||||||
|  |   STILL_ENERGY = 14, | ||||||
|  |   DETECT_DISTANCE_LOW = 15, | ||||||
|  |   DETECT_DISTANCE_HIGH = 16, | ||||||
|  |   MOVING_SENSOR_START = 19, | ||||||
|  |   STILL_SENSOR_START = 28, | ||||||
|  |   LIGHT_SENSOR = 37, | ||||||
|  |   OUT_PIN_SENSOR = 38, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum PeriodicDataValue : uint8_t { | ||||||
|  |   HEAD = 0xAA, | ||||||
|  |   END = 0x55, | ||||||
|  |   CHECK = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum AckDataStructure : uint8_t { | ||||||
|  |   COMMAND = 6, | ||||||
|  |   COMMAND_STATUS = 7, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Memory-efficient lookup tables | ||||||
|  | struct StringToUint8 { | ||||||
|  |   const char *str; | ||||||
|  |   uint8_t value; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct Uint8ToString { | ||||||
|  |   uint8_t value; | ||||||
|  |   const char *str; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr StringToUint8 BAUD_RATES_BY_STR[] = { | ||||||
|  |     {"9600", BAUD_RATE_9600},     {"19200", BAUD_RATE_19200},   {"38400", BAUD_RATE_38400}, | ||||||
|  |     {"57600", BAUD_RATE_57600},   {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400}, | ||||||
|  |     {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[] = { | ||||||
|  |     {"0.2m", DISTANCE_RESOLUTION_0_2}, | ||||||
|  |     {"0.75m", DISTANCE_RESOLUTION_0_75}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[] = { | ||||||
|  |     {DISTANCE_RESOLUTION_0_2, "0.2m"}, | ||||||
|  |     {DISTANCE_RESOLUTION_0_75, "0.75m"}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[] = { | ||||||
|  |     {"off", LIGHT_FUNCTION_OFF}, | ||||||
|  |     {"below", LIGHT_FUNCTION_BELOW}, | ||||||
|  |     {"above", LIGHT_FUNCTION_ABOVE}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[] = { | ||||||
|  |     {LIGHT_FUNCTION_OFF, "off"}, | ||||||
|  |     {LIGHT_FUNCTION_BELOW, "below"}, | ||||||
|  |     {LIGHT_FUNCTION_ABOVE, "above"}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[] = { | ||||||
|  |     {"low", OUT_PIN_LEVEL_LOW}, | ||||||
|  |     {"high", OUT_PIN_LEVEL_HIGH}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = { | ||||||
|  |     {OUT_PIN_LEVEL_LOW, "low"}, | ||||||
|  |     {OUT_PIN_LEVEL_HIGH, "high"}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Helper functions for lookups | ||||||
|  | template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { | ||||||
|  |   for (const auto &entry : arr) { | ||||||
|  |     if (str == entry.str) | ||||||
|  |       return entry.value; | ||||||
|  |   } | ||||||
|  |   return 0xFF;  // Not found | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) { | ||||||
|  |   for (const auto &entry : arr) { | ||||||
|  |     if (value == entry.value) | ||||||
|  |       return entry.str; | ||||||
|  |   } | ||||||
|  |   return "";  // Not found | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Commands | ||||||
|  | static const uint8_t CMD_ENABLE_CONF = 0xFF; | ||||||
|  | static const uint8_t CMD_DISABLE_CONF = 0xFE; | ||||||
|  | static const uint8_t CMD_ENABLE_ENG = 0x62; | ||||||
|  | static const uint8_t CMD_DISABLE_ENG = 0x63; | ||||||
|  | static const uint8_t CMD_MAXDIST_DURATION = 0x60; | ||||||
|  | static const uint8_t CMD_QUERY = 0x61; | ||||||
|  | static const uint8_t CMD_GATE_SENS = 0x64; | ||||||
|  | static const uint8_t CMD_VERSION = 0xA0; | ||||||
|  | static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB; | ||||||
|  | static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA; | ||||||
|  | static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE; | ||||||
|  | static const uint8_t CMD_SET_LIGHT_CONTROL = 0xAD; | ||||||
|  | static const uint8_t CMD_SET_BAUD_RATE = 0xA1; | ||||||
|  | static const uint8_t CMD_BT_PASSWORD = 0xA9; | ||||||
|  | static const uint8_t CMD_MAC = 0xA5; | ||||||
|  | static const uint8_t CMD_RESET = 0xA2; | ||||||
|  | static const uint8_t CMD_RESTART = 0xA3; | ||||||
|  | static const uint8_t CMD_BLUETOOTH = 0xA4; | ||||||
|  | // Commands values | ||||||
|  | static const uint8_t CMD_MAX_MOVE_VALUE = 0x00; | ||||||
|  | static const uint8_t CMD_MAX_STILL_VALUE = 0x01; | ||||||
|  | static const uint8_t CMD_DURATION_VALUE = 0x02; | ||||||
|  | // Command Header & Footer | ||||||
|  | static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA}; | ||||||
|  | static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01}; | ||||||
|  | // Data Header & Footer | ||||||
|  | static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1}; | ||||||
|  | static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5}; | ||||||
|  |  | ||||||
|  | static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; } | ||||||
|  |  | ||||||
| void LD2410Component::dump_config() { | void LD2410Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "LD2410:"); |   ESP_LOGCONFIG(TAG, "LD2410:"); | ||||||
| @@ -73,10 +230,10 @@ void LD2410Component::dump_config() { | |||||||
| #endif | #endif | ||||||
|   this->read_all_info(); |   this->read_all_info(); | ||||||
|   ESP_LOGCONFIG(TAG, |   ESP_LOGCONFIG(TAG, | ||||||
|                 "  Throttle_ : %ums\n" |                 "  Throttle: %ums\n" | ||||||
|                 "  MAC Address : %s\n" |                 "  MAC address: %s\n" | ||||||
|                 "  Firmware Version : %s", |                 "  Firmware version: %s", | ||||||
|                 this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str())); |                 this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2410Component::setup() { | void LD2410Component::setup() { | ||||||
| @@ -153,7 +310,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { | |||||||
|   /* |   /* | ||||||
|     Reduce data update rate to prevent home assistant database size grow fast |     Reduce data update rate to prevent home assistant database size grow fast | ||||||
|   */ |   */ | ||||||
|   int32_t current_millis = millis(); |   int32_t current_millis = App.get_loop_component_start_time(); | ||||||
|   if (current_millis - last_periodic_millis_ < this->throttle_) |   if (current_millis - last_periodic_millis_ < this->throttle_) | ||||||
|     return; |     return; | ||||||
|   last_periodic_millis_ = current_millis; |   last_periodic_millis_ = current_millis; | ||||||
| @@ -198,7 +355,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { | |||||||
|   */ |   */ | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|   if (this->moving_target_distance_sensor_ != nullptr) { |   if (this->moving_target_distance_sensor_ != nullptr) { | ||||||
|     int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]); |     int new_moving_target_distance = ld2410::two_byte_to_int(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]); | ||||||
|     if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance) |     if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance) | ||||||
|       this->moving_target_distance_sensor_->publish_state(new_moving_target_distance); |       this->moving_target_distance_sensor_->publish_state(new_moving_target_distance); | ||||||
|   } |   } | ||||||
| @@ -208,7 +365,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { | |||||||
|       this->moving_target_energy_sensor_->publish_state(new_moving_target_energy); |       this->moving_target_energy_sensor_->publish_state(new_moving_target_energy); | ||||||
|   } |   } | ||||||
|   if (this->still_target_distance_sensor_ != nullptr) { |   if (this->still_target_distance_sensor_ != nullptr) { | ||||||
|     int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]); |     int new_still_target_distance = ld2410::two_byte_to_int(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]); | ||||||
|     if (this->still_target_distance_sensor_->get_state() != new_still_target_distance) |     if (this->still_target_distance_sensor_->get_state() != new_still_target_distance) | ||||||
|       this->still_target_distance_sensor_->publish_state(new_still_target_distance); |       this->still_target_distance_sensor_->publish_state(new_still_target_distance); | ||||||
|   } |   } | ||||||
| @@ -218,7 +375,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { | |||||||
|       this->still_target_energy_sensor_->publish_state(new_still_target_energy); |       this->still_target_energy_sensor_->publish_state(new_still_target_energy); | ||||||
|   } |   } | ||||||
|   if (this->detection_distance_sensor_ != nullptr) { |   if (this->detection_distance_sensor_ != nullptr) { | ||||||
|     int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]); |     int new_detect_distance = ld2410::two_byte_to_int(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]); | ||||||
|     if (this->detection_distance_sensor_->get_state() != new_detect_distance) |     if (this->detection_distance_sensor_->get_state() != new_detect_distance) | ||||||
|       this->detection_distance_sensor_->publish_state(new_detect_distance); |       this->detection_distance_sensor_->publish_state(new_detect_distance); | ||||||
|   } |   } | ||||||
| @@ -280,40 +437,6 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| const char VERSION_FMT[] = "%u.%02X.%02X%02X%02X%02X"; |  | ||||||
|  |  | ||||||
| std::string format_version(uint8_t *buffer) { |  | ||||||
|   std::string::size_type version_size = 256; |  | ||||||
|   std::string version; |  | ||||||
|   do { |  | ||||||
|     version.resize(version_size + 1); |  | ||||||
|     version_size = std::snprintf(&version[0], version.size(), VERSION_FMT, buffer[13], buffer[12], buffer[17], |  | ||||||
|                                  buffer[16], buffer[15], buffer[14]); |  | ||||||
|   } while (version_size + 1 > version.size()); |  | ||||||
|   version.resize(version_size); |  | ||||||
|   return version; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X"; |  | ||||||
|  |  | ||||||
| const std::string UNKNOWN_MAC("unknown"); |  | ||||||
| const std::string NO_MAC("08:05:04:03:02:01"); |  | ||||||
|  |  | ||||||
| std::string format_mac(uint8_t *buffer) { |  | ||||||
|   std::string::size_type mac_size = 256; |  | ||||||
|   std::string mac; |  | ||||||
|   do { |  | ||||||
|     mac.resize(mac_size + 1); |  | ||||||
|     mac_size = std::snprintf(&mac[0], mac.size(), MAC_FMT, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], |  | ||||||
|                              buffer[15]); |  | ||||||
|   } while (mac_size + 1 > mac.size()); |  | ||||||
|   mac.resize(mac_size); |  | ||||||
|   if (mac == NO_MAC) { |  | ||||||
|     return UNKNOWN_MAC; |  | ||||||
|   } |  | ||||||
|   return mac; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| std::function<void(void)> set_number_value(number::Number *n, float value) { | std::function<void(void)> set_number_value(number::Number *n, float value) { | ||||||
|   float normalized_value = value * 1.0; |   float normalized_value = value * 1.0; | ||||||
| @@ -328,40 +451,40 @@ std::function<void(void)> set_number_value(number::Number *n, float value) { | |||||||
| bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | ||||||
|   ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]); |   ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]); | ||||||
|   if (len < 10) { |   if (len < 10) { | ||||||
|     ESP_LOGE(TAG, "Error with last command : incorrect length"); |     ESP_LOGE(TAG, "Invalid length"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // check 4 frame start bytes |   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // check 4 frame start bytes | ||||||
|     ESP_LOGE(TAG, "Error with last command : incorrect Header"); |     ESP_LOGE(TAG, "Invalid header"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[COMMAND_STATUS] != 0x01) { |   if (buffer[COMMAND_STATUS] != 0x01) { | ||||||
|     ESP_LOGE(TAG, "Error with last command : status != 0x01"); |     ESP_LOGE(TAG, "Invalid status"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) { |   if (ld2410::two_byte_to_int(buffer[8], buffer[9]) != 0x00) { | ||||||
|     ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]); |     ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   switch (buffer[COMMAND]) { |   switch (buffer[COMMAND]) { | ||||||
|     case lowbyte(CMD_ENABLE_CONF): |     case lowbyte(CMD_ENABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Handled Enable conf command"); |       ESP_LOGV(TAG, "Enable conf"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_DISABLE_CONF): |     case lowbyte(CMD_DISABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Handled Disabled conf command"); |       ESP_LOGV(TAG, "Disabled conf"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_BAUD_RATE): |     case lowbyte(CMD_SET_BAUD_RATE): | ||||||
|       ESP_LOGV(TAG, "Handled baud rate change command"); |       ESP_LOGV(TAG, "Baud rate change"); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->baud_rate_select_ != nullptr) { |       if (this->baud_rate_select_ != nullptr) { | ||||||
|         ESP_LOGE(TAG, "Change baud rate component config to %s and reinstall", this->baud_rate_select_->state.c_str()); |         ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); | ||||||
|       } |       } | ||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_VERSION): |     case lowbyte(CMD_VERSION): | ||||||
|       this->version_ = format_version(buffer); |       this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]); | ||||||
|       ESP_LOGV(TAG, "FW Version is: %s", const_cast<char *>(this->version_.c_str())); |       ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str()); | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|       if (this->version_text_sensor_ != nullptr) { |       if (this->version_text_sensor_ != nullptr) { | ||||||
|         this->version_text_sensor_->publish_state(this->version_); |         this->version_text_sensor_->publish_state(this->version_); | ||||||
| @@ -370,8 +493,8 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): { |     case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): { | ||||||
|       std::string distance_resolution = |       std::string distance_resolution = | ||||||
|           DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11])); |           find_str(DISTANCE_RESOLUTIONS_BY_UINT, ld2410::two_byte_to_int(buffer[10], buffer[11])); | ||||||
|       ESP_LOGV(TAG, "Distance resolution is: %s", const_cast<char *>(distance_resolution.c_str())); |       ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution.c_str()); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->distance_resolution_select_ != nullptr && |       if (this->distance_resolution_select_ != nullptr && | ||||||
|           this->distance_resolution_select_->state != distance_resolution) { |           this->distance_resolution_select_->state != distance_resolution) { | ||||||
| @@ -380,12 +503,12 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
| #endif | #endif | ||||||
|     } break; |     } break; | ||||||
|     case lowbyte(CMD_QUERY_LIGHT_CONTROL): { |     case lowbyte(CMD_QUERY_LIGHT_CONTROL): { | ||||||
|       this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]); |       this->light_function_ = find_str(LIGHT_FUNCTIONS_BY_UINT, buffer[10]); | ||||||
|       this->light_threshold_ = buffer[11] * 1.0; |       this->light_threshold_ = buffer[11] * 1.0; | ||||||
|       this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]); |       this->out_pin_level_ = find_str(OUT_PIN_LEVELS_BY_UINT, buffer[12]); | ||||||
|       ESP_LOGV(TAG, "Light function is: %s", const_cast<char *>(this->light_function_.c_str())); |       ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str())); | ||||||
|       ESP_LOGV(TAG, "Light threshold is: %f", this->light_threshold_); |       ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_); | ||||||
|       ESP_LOGV(TAG, "Out pin level is: %s", const_cast<char *>(this->out_pin_level_.c_str())); |       ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str())); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) { |       if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) { | ||||||
|         this->light_function_select_->publish_state(this->light_function_); |         this->light_function_select_->publish_state(this->light_function_); | ||||||
| @@ -406,33 +529,33 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       if (len < 20) { |       if (len < 20) { | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       this->mac_ = format_mac(buffer); |       this->mac_ = format_mac_address_pretty(&buffer[10]); | ||||||
|       ESP_LOGV(TAG, "MAC Address is: %s", const_cast<char *>(this->mac_.c_str())); |       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|       if (this->mac_text_sensor_ != nullptr) { |       if (this->mac_text_sensor_ != nullptr) { | ||||||
|         this->mac_text_sensor_->publish_state(this->mac_); |         this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_); | ||||||
|       } |       } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->bluetooth_switch_ != nullptr) { |       if (this->bluetooth_switch_ != nullptr) { | ||||||
|         this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC); |         this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC); | ||||||
|       } |       } | ||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_GATE_SENS): |     case lowbyte(CMD_GATE_SENS): | ||||||
|       ESP_LOGV(TAG, "Handled sensitivity command"); |       ESP_LOGV(TAG, "Sensitivity"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_BLUETOOTH): |     case lowbyte(CMD_BLUETOOTH): | ||||||
|       ESP_LOGV(TAG, "Handled bluetooth command"); |       ESP_LOGV(TAG, "Bluetooth"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_DISTANCE_RESOLUTION): |     case lowbyte(CMD_SET_DISTANCE_RESOLUTION): | ||||||
|       ESP_LOGV(TAG, "Handled set distance resolution command"); |       ESP_LOGV(TAG, "Set distance resolution"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_LIGHT_CONTROL): |     case lowbyte(CMD_SET_LIGHT_CONTROL): | ||||||
|       ESP_LOGV(TAG, "Handled set light control command"); |       ESP_LOGV(TAG, "Set light control"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_BT_PASSWORD): |     case lowbyte(CMD_BT_PASSWORD): | ||||||
|       ESP_LOGV(TAG, "Handled set bluetooth password command"); |       ESP_LOGV(TAG, "Set bluetooth password"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_QUERY):  // Query parameters response |     case lowbyte(CMD_QUERY):  // Query parameters response | ||||||
|     { |     { | ||||||
| @@ -461,7 +584,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       /* |       /* | ||||||
|         None Duration: 33~34th bytes |         None Duration: 33~34th bytes | ||||||
|       */ |       */ | ||||||
|       updates.push_back(set_number_value(this->timeout_number_, this->two_byte_to_int_(buffer[32], buffer[33]))); |       updates.push_back(set_number_value(this->timeout_number_, ld2410::two_byte_to_int(buffer[32], buffer[33]))); | ||||||
|       for (auto &update : updates) { |       for (auto &update : updates) { | ||||||
|         update(); |         update(); | ||||||
|       } |       } | ||||||
| @@ -518,21 +641,21 @@ void LD2410Component::set_bluetooth(bool enable) { | |||||||
|  |  | ||||||
| void LD2410Component::set_distance_resolution(const std::string &state) { | void LD2410Component::set_distance_resolution(const std::string &state) { | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
|   uint8_t cmd_value[2] = {DISTANCE_RESOLUTION_ENUM_TO_INT.at(state), 0x00}; |   uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00}; | ||||||
|   this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2); |   this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2); | ||||||
|   this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); |   this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2410Component::set_baud_rate(const std::string &state) { | void LD2410Component::set_baud_rate(const std::string &state) { | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
|   uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00}; |   uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; | ||||||
|   this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); |   this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); | ||||||
|   this->set_timeout(200, [this]() { this->restart_(); }); |   this->set_timeout(200, [this]() { this->restart_(); }); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2410Component::set_bluetooth_password(const std::string &password) { | void LD2410Component::set_bluetooth_password(const std::string &password) { | ||||||
|   if (password.length() != 6) { |   if (password.length() != 6) { | ||||||
|     ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str()); |     ESP_LOGE(TAG, "Password must be exactly 6 chars"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
| @@ -544,7 +667,7 @@ void LD2410Component::set_bluetooth_password(const std::string &password) { | |||||||
|  |  | ||||||
| void LD2410Component::set_engineering_mode(bool enable) { | void LD2410Component::set_engineering_mode(bool enable) { | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
|   last_engineering_mode_change_millis_ = millis(); |   last_engineering_mode_change_millis_ = App.get_loop_component_start_time(); | ||||||
|   uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG; |   uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG; | ||||||
|   this->send_command_(cmd, nullptr, 0); |   this->send_command_(cmd, nullptr, 0); | ||||||
|   this->set_config_mode_(false); |   this->set_config_mode_(false); | ||||||
| @@ -659,9 +782,9 @@ void LD2410Component::set_light_out_control() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
|   uint8_t light_function = LIGHT_FUNCTION_ENUM_TO_INT.at(this->light_function_); |   uint8_t light_function = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_); | ||||||
|   uint8_t light_threshold = static_cast<uint8_t>(this->light_threshold_); |   uint8_t light_threshold = static_cast<uint8_t>(this->light_threshold_); | ||||||
|   uint8_t out_pin_level = OUT_PIN_LEVEL_ENUM_TO_INT.at(this->out_pin_level_); |   uint8_t out_pin_level = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_); | ||||||
|   uint8_t value[4] = {light_function, light_threshold, out_pin_level, 0x00}; |   uint8_t value[4] = {light_function, light_threshold, out_pin_level, 0x00}; | ||||||
|   this->send_command_(CMD_SET_LIGHT_CONTROL, value, 4); |   this->send_command_(CMD_SET_LIGHT_CONTROL, value, 4); | ||||||
|   delay(50);  // NOLINT |   delay(50);  // NOLINT | ||||||
|   | |||||||
| @@ -26,114 +26,9 @@ | |||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #include <map> |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ld2410 { | namespace ld2410 { | ||||||
|  |  | ||||||
| #define CHECK_BIT(var, pos) (((var) >> (pos)) & 1) |  | ||||||
|  |  | ||||||
| // Commands |  | ||||||
| static const uint8_t CMD_ENABLE_CONF = 0x00FF; |  | ||||||
| static const uint8_t CMD_DISABLE_CONF = 0x00FE; |  | ||||||
| static const uint8_t CMD_ENABLE_ENG = 0x0062; |  | ||||||
| static const uint8_t CMD_DISABLE_ENG = 0x0063; |  | ||||||
| static const uint8_t CMD_MAXDIST_DURATION = 0x0060; |  | ||||||
| static const uint8_t CMD_QUERY = 0x0061; |  | ||||||
| static const uint8_t CMD_GATE_SENS = 0x0064; |  | ||||||
| static const uint8_t CMD_VERSION = 0x00A0; |  | ||||||
| static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x00AB; |  | ||||||
| static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x00AA; |  | ||||||
| static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0x00AE; |  | ||||||
| static const uint8_t CMD_SET_LIGHT_CONTROL = 0x00AD; |  | ||||||
| static const uint8_t CMD_SET_BAUD_RATE = 0x00A1; |  | ||||||
| static const uint8_t CMD_BT_PASSWORD = 0x00A9; |  | ||||||
| static const uint8_t CMD_MAC = 0x00A5; |  | ||||||
| static const uint8_t CMD_RESET = 0x00A2; |  | ||||||
| static const uint8_t CMD_RESTART = 0x00A3; |  | ||||||
| static const uint8_t CMD_BLUETOOTH = 0x00A4; |  | ||||||
|  |  | ||||||
| enum BaudRateStructure : uint8_t { |  | ||||||
|   BAUD_RATE_9600 = 1, |  | ||||||
|   BAUD_RATE_19200 = 2, |  | ||||||
|   BAUD_RATE_38400 = 3, |  | ||||||
|   BAUD_RATE_57600 = 4, |  | ||||||
|   BAUD_RATE_115200 = 5, |  | ||||||
|   BAUD_RATE_230400 = 6, |  | ||||||
|   BAUD_RATE_256000 = 7, |  | ||||||
|   BAUD_RATE_460800 = 8 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{ |  | ||||||
|     {"9600", BAUD_RATE_9600},     {"19200", BAUD_RATE_19200},   {"38400", BAUD_RATE_38400}, |  | ||||||
|     {"57600", BAUD_RATE_57600},   {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400}, |  | ||||||
|     {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}}; |  | ||||||
|  |  | ||||||
| enum DistanceResolutionStructure : uint8_t { DISTANCE_RESOLUTION_0_2 = 0x01, DISTANCE_RESOLUTION_0_75 = 0x00 }; |  | ||||||
|  |  | ||||||
| static const std::map<std::string, uint8_t> DISTANCE_RESOLUTION_ENUM_TO_INT{{"0.2m", DISTANCE_RESOLUTION_0_2}, |  | ||||||
|                                                                             {"0.75m", DISTANCE_RESOLUTION_0_75}}; |  | ||||||
| static const std::map<uint8_t, std::string> DISTANCE_RESOLUTION_INT_TO_ENUM{{DISTANCE_RESOLUTION_0_2, "0.2m"}, |  | ||||||
|                                                                             {DISTANCE_RESOLUTION_0_75, "0.75m"}}; |  | ||||||
|  |  | ||||||
| enum LightFunctionStructure : uint8_t { |  | ||||||
|   LIGHT_FUNCTION_OFF = 0x00, |  | ||||||
|   LIGHT_FUNCTION_BELOW = 0x01, |  | ||||||
|   LIGHT_FUNCTION_ABOVE = 0x02 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| static const std::map<std::string, uint8_t> LIGHT_FUNCTION_ENUM_TO_INT{ |  | ||||||
|     {"off", LIGHT_FUNCTION_OFF}, {"below", LIGHT_FUNCTION_BELOW}, {"above", LIGHT_FUNCTION_ABOVE}}; |  | ||||||
| static const std::map<uint8_t, std::string> LIGHT_FUNCTION_INT_TO_ENUM{ |  | ||||||
|     {LIGHT_FUNCTION_OFF, "off"}, {LIGHT_FUNCTION_BELOW, "below"}, {LIGHT_FUNCTION_ABOVE, "above"}}; |  | ||||||
|  |  | ||||||
| enum OutPinLevelStructure : uint8_t { OUT_PIN_LEVEL_LOW = 0x00, OUT_PIN_LEVEL_HIGH = 0x01 }; |  | ||||||
|  |  | ||||||
| static const std::map<std::string, uint8_t> OUT_PIN_LEVEL_ENUM_TO_INT{{"low", OUT_PIN_LEVEL_LOW}, |  | ||||||
|                                                                       {"high", OUT_PIN_LEVEL_HIGH}}; |  | ||||||
| static const std::map<uint8_t, std::string> OUT_PIN_LEVEL_INT_TO_ENUM{{OUT_PIN_LEVEL_LOW, "low"}, |  | ||||||
|                                                                       {OUT_PIN_LEVEL_HIGH, "high"}}; |  | ||||||
|  |  | ||||||
| // Commands values |  | ||||||
| static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000; |  | ||||||
| static const uint8_t CMD_MAX_STILL_VALUE = 0x0001; |  | ||||||
| static const uint8_t CMD_DURATION_VALUE = 0x0002; |  | ||||||
| // Command Header & Footer |  | ||||||
| static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA}; |  | ||||||
| static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01}; |  | ||||||
| // Data Header & Footer |  | ||||||
| static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1}; |  | ||||||
| static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5}; |  | ||||||
| /* |  | ||||||
| Data Type: 6th byte |  | ||||||
| Target states: 9th byte |  | ||||||
|     Moving target distance: 10~11th bytes |  | ||||||
|     Moving target energy: 12th byte |  | ||||||
|     Still target distance: 13~14th bytes |  | ||||||
|     Still target energy: 15th byte |  | ||||||
|     Detect distance: 16~17th bytes |  | ||||||
| */ |  | ||||||
| enum PeriodicDataStructure : uint8_t { |  | ||||||
|   DATA_TYPES = 6, |  | ||||||
|   TARGET_STATES = 8, |  | ||||||
|   MOVING_TARGET_LOW = 9, |  | ||||||
|   MOVING_TARGET_HIGH = 10, |  | ||||||
|   MOVING_ENERGY = 11, |  | ||||||
|   STILL_TARGET_LOW = 12, |  | ||||||
|   STILL_TARGET_HIGH = 13, |  | ||||||
|   STILL_ENERGY = 14, |  | ||||||
|   DETECT_DISTANCE_LOW = 15, |  | ||||||
|   DETECT_DISTANCE_HIGH = 16, |  | ||||||
|   MOVING_SENSOR_START = 19, |  | ||||||
|   STILL_SENSOR_START = 28, |  | ||||||
|   LIGHT_SENSOR = 37, |  | ||||||
|   OUT_PIN_SENSOR = 38, |  | ||||||
| }; |  | ||||||
| enum PeriodicDataValue : uint8_t { HEAD = 0xAA, END = 0x55, CHECK = 0x00 }; |  | ||||||
|  |  | ||||||
| enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 }; |  | ||||||
|  |  | ||||||
| //  char cmd[2] = {enable ? 0xFF : 0xFE, 0x00}; |  | ||||||
| class LD2410Component : public Component, public uart::UARTDevice { | class LD2410Component : public Component, public uart::UARTDevice { | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|   SUB_SENSOR(moving_target_distance) |   SUB_SENSOR(moving_target_distance) | ||||||
| @@ -176,7 +71,6 @@ class LD2410Component : public Component, public uart::UARTDevice { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   LD2410Component(); |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
| @@ -202,7 +96,6 @@ class LD2410Component : public Component, public uart::UARTDevice { | |||||||
|   void factory_reset(); |   void factory_reset(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; } |  | ||||||
|   void send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len); |   void send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len); | ||||||
|   void set_config_mode_(bool enable); |   void set_config_mode_(bool enable); | ||||||
|   void handle_periodic_data_(uint8_t *buffer, int len); |   void handle_periodic_data_(uint8_t *buffer, int len); | ||||||
| @@ -215,14 +108,14 @@ class LD2410Component : public Component, public uart::UARTDevice { | |||||||
|   void get_light_control_(); |   void get_light_control_(); | ||||||
|   void restart_(); |   void restart_(); | ||||||
|  |  | ||||||
|   int32_t last_periodic_millis_ = millis(); |   int32_t last_periodic_millis_ = 0; | ||||||
|   int32_t last_engineering_mode_change_millis_ = millis(); |   int32_t last_engineering_mode_change_millis_ = 0; | ||||||
|   uint16_t throttle_; |   uint16_t throttle_; | ||||||
|  |   float light_threshold_ = -1; | ||||||
|   std::string version_; |   std::string version_; | ||||||
|   std::string mac_; |   std::string mac_; | ||||||
|   std::string out_pin_level_; |   std::string out_pin_level_; | ||||||
|   std::string light_function_; |   std::string light_function_; | ||||||
|   float light_threshold_ = -1; |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|   std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(9); |   std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(9); | ||||||
|   std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(9); |   std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(9); | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #include "ld2420.h" | #include "ld2420.h" | ||||||
|  | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -40,7 +41,7 @@ There are three documented parameters for modes: | |||||||
|   00 04 = Energy output mode |   00 04 = Energy output mode | ||||||
|     This mode outputs detailed signal energy values for each gate and the target distance. |     This mode outputs detailed signal energy values for each gate and the target distance. | ||||||
|     The data format consist of the following. |     The data format consist of the following. | ||||||
|     Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF |     Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF | ||||||
|     HH HH HH HH LL LL PP DD DD EE EE .. 16x   .. FF FF FF FF |     HH HH HH HH LL LL PP DD DD EE EE .. 16x   .. FF FF FF FF | ||||||
|     F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5 |     F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5 | ||||||
|   00 00 = debug output mode |   00 00 = debug output mode | ||||||
| @@ -67,10 +68,10 @@ float LD2420Component::get_setup_priority() const { return setup_priority::BUS; | |||||||
| void LD2420Component::dump_config() { | void LD2420Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, |   ESP_LOGCONFIG(TAG, | ||||||
|                 "LD2420:\n" |                 "LD2420:\n" | ||||||
|                 "  Firmware Version : %7s\n" |                 "  Firmware version: %7s", | ||||||
|                 "LD2420 Number:", |  | ||||||
|                 this->ld2420_firmware_ver_); |                 this->ld2420_firmware_ver_); | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|  |   ESP_LOGCONFIG(TAG, "Number:"); | ||||||
|   LOG_NUMBER(TAG, "  Gate Timeout:", this->gate_timeout_number_); |   LOG_NUMBER(TAG, "  Gate Timeout:", this->gate_timeout_number_); | ||||||
|   LOG_NUMBER(TAG, "  Gate Max Distance:", this->max_gate_distance_number_); |   LOG_NUMBER(TAG, "  Gate Max Distance:", this->max_gate_distance_number_); | ||||||
|   LOG_NUMBER(TAG, "  Gate Min Distance:", this->min_gate_distance_number_); |   LOG_NUMBER(TAG, "  Gate Min Distance:", this->min_gate_distance_number_); | ||||||
| @@ -86,10 +87,10 @@ void LD2420Component::dump_config() { | |||||||
|   LOG_BUTTON(TAG, "  Factory Reset:", this->factory_reset_button_); |   LOG_BUTTON(TAG, "  Factory Reset:", this->factory_reset_button_); | ||||||
|   LOG_BUTTON(TAG, "  Restart Module:", this->restart_module_button_); |   LOG_BUTTON(TAG, "  Restart Module:", this->restart_module_button_); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 Select:"); |   ESP_LOGCONFIG(TAG, "Select:"); | ||||||
|   LOG_SELECT(TAG, "  Operating Mode", this->operating_selector_); |   LOG_SELECT(TAG, "  Operating Mode", this->operating_selector_); | ||||||
|   if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { |   if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { | ||||||
|     ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); |     ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -102,7 +103,7 @@ uint8_t LD2420Component::calc_checksum(void *data, size_t size) { | |||||||
|   return checksum; |   return checksum; | ||||||
| } | } | ||||||
|  |  | ||||||
| int LD2420Component::get_firmware_int_(const char *version_string) { | int LD2420Component::get_firmware_int(const char *version_string) { | ||||||
|   std::string version_str = version_string; |   std::string version_str = version_string; | ||||||
|   if (version_str[0] == 'v') { |   if (version_str[0] == 'v') { | ||||||
|     version_str = version_str.substr(1); |     version_str = version_str.substr(1); | ||||||
| @@ -115,7 +116,7 @@ int LD2420Component::get_firmware_int_(const char *version_string) { | |||||||
| void LD2420Component::setup() { | void LD2420Component::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Running setup"); |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { |   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -127,7 +128,7 @@ void LD2420Component::setup() { | |||||||
|   const char *pfw = this->ld2420_firmware_ver_; |   const char *pfw = this->ld2420_firmware_ver_; | ||||||
|   std::string fw_str(pfw); |   std::string fw_str(pfw); | ||||||
|  |  | ||||||
|   for (auto &listener : listeners_) { |   for (auto &listener : this->listeners_) { | ||||||
|     listener->on_fw_version(fw_str); |     listener->on_fw_version(fw_str); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -137,11 +138,11 @@ void LD2420Component::setup() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); |   memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); | ||||||
|   if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { |   if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { | ||||||
|     this->set_operating_mode(OP_SIMPLE_MODE_STRING); |     this->set_operating_mode(OP_SIMPLE_MODE_STRING); | ||||||
|     this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); |     this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); | ||||||
|     this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); |     this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); | ||||||
|     ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); |     ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_); | ||||||
|   } else { |   } else { | ||||||
|     this->set_mode_(CMD_SYSTEM_MODE_ENERGY); |     this->set_mode_(CMD_SYSTEM_MODE_ENERGY); | ||||||
|     this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); |     this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); | ||||||
| @@ -151,18 +152,17 @@ void LD2420Component::setup() { | |||||||
| #endif | #endif | ||||||
|   this->set_system_mode(this->system_mode_); |   this->set_system_mode(this->system_mode_); | ||||||
|   this->set_config_mode(false); |   this->set_config_mode(false); | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 setup complete."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::apply_config_action() { | void LD2420Component::apply_config_action() { | ||||||
|   const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config)); |   const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config)); | ||||||
|   if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) { |   if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) { | ||||||
|     ESP_LOGCONFIG(TAG, "No configuration change detected"); |     ESP_LOGD(TAG, "No configuration change detected"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   ESP_LOGCONFIG(TAG, "Reconfiguring LD2420"); |   ESP_LOGD(TAG, "Reconfiguring"); | ||||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { |   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -178,13 +178,12 @@ void LD2420Component::apply_config_action() { | |||||||
|   this->set_system_mode(this->system_mode_); |   this->set_system_mode(this->system_mode_); | ||||||
|   this->set_config_mode(false);  // Disable config mode to save new values in LD2420 nvm |   this->set_config_mode(false);  // Disable config mode to save new values in LD2420 nvm | ||||||
|   this->set_operating_mode(OP_NORMAL_MODE_STRING); |   this->set_operating_mode(OP_NORMAL_MODE_STRING); | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 reconfig complete."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::factory_reset_action() { | void LD2420Component::factory_reset_action() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting factory defaults"); |   ESP_LOGD(TAG, "Setting factory defaults"); | ||||||
|   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { |   if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { | ||||||
|     ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -207,18 +206,16 @@ void LD2420Component::factory_reset_action() { | |||||||
|   this->init_gate_config_numbers(); |   this->init_gate_config_numbers(); | ||||||
|   this->refresh_gate_config_numbers(); |   this->refresh_gate_config_numbers(); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 factory reset complete."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::restart_module_action() { | void LD2420Component::restart_module_action() { | ||||||
|   ESP_LOGCONFIG(TAG, "Restarting LD2420 module"); |   ESP_LOGD(TAG, "Restarting"); | ||||||
|   this->send_module_restart(); |   this->send_module_restart(); | ||||||
|   this->set_timeout(250, [this]() { |   this->set_timeout(250, [this]() { | ||||||
|     this->set_config_mode(true); |     this->set_config_mode(true); | ||||||
|     this->set_system_mode(system_mode_); |     this->set_system_mode(this->system_mode_); | ||||||
|     this->set_config_mode(false); |     this->set_config_mode(false); | ||||||
|   }); |   }); | ||||||
|   ESP_LOGCONFIG(TAG, "LD2420 Restarted."); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::revert_config_action() { | void LD2420Component::revert_config_action() { | ||||||
| @@ -226,18 +223,18 @@ void LD2420Component::revert_config_action() { | |||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|   this->init_gate_config_numbers(); |   this->init_gate_config_numbers(); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, "Reverted config number edits."); |   ESP_LOGD(TAG, "Reverted config number edits"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::loop() { | void LD2420Component::loop() { | ||||||
|   // If there is a active send command do not process it here, the send command call will handle it. |   // If there is a active send command do not process it here, the send command call will handle it. | ||||||
|   if (!get_cmd_active_()) { |   if (!this->get_cmd_active_()) { | ||||||
|     if (!available()) |     if (!this->available()) | ||||||
|       return; |       return; | ||||||
|     static uint8_t buffer[2048]; |     static uint8_t buffer[2048]; | ||||||
|     static uint8_t rx_data; |     static uint8_t rx_data; | ||||||
|     while (available()) { |     while (this->available()) { | ||||||
|       rx_data = read(); |       rx_data = this->read(); | ||||||
|       this->readline_(rx_data, buffer, sizeof(buffer)); |       this->readline_(rx_data, buffer, sizeof(buffer)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -292,7 +289,7 @@ void LD2420Component::report_gate_data() { | |||||||
|  |  | ||||||
| void LD2420Component::set_operating_mode(const std::string &state) { | void LD2420Component::set_operating_mode(const std::string &state) { | ||||||
|   // If unsupported firmware ignore mode select |   // If unsupported firmware ignore mode select | ||||||
|   if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { |   if (LD2420Component::get_firmware_int(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { | ||||||
|     this->current_operating_mode = OP_MODE_TO_UINT.at(state); |     this->current_operating_mode = OP_MODE_TO_UINT.at(state); | ||||||
|     // Entering Auto Calibrate we need to clear the privoiuos data collection |     // Entering Auto Calibrate we need to clear the privoiuos data collection | ||||||
|     this->operating_selector_->publish_state(state); |     this->operating_selector_->publish_state(state); | ||||||
| @@ -365,13 +362,13 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Resonable refresh rate for home assistant database size health |   // Resonable refresh rate for home assistant database size health | ||||||
|   const int32_t current_millis = millis(); |   const int32_t current_millis = App.get_loop_component_start_time(); | ||||||
|   if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) |   if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) | ||||||
|     return; |     return; | ||||||
|   this->last_periodic_millis = current_millis; |   this->last_periodic_millis = current_millis; | ||||||
|   for (auto &listener : this->listeners_) { |   for (auto &listener : this->listeners_) { | ||||||
|     listener->on_distance(get_distance_()); |     listener->on_distance(this->get_distance_()); | ||||||
|     listener->on_presence(get_presence_()); |     listener->on_presence(this->get_presence_()); | ||||||
|     listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0])); |     listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0])); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -392,9 +389,9 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { | |||||||
|   char outbuf[bufsize]{0}; |   char outbuf[bufsize]{0}; | ||||||
|   while (true) { |   while (true) { | ||||||
|     if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') { |     if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') { | ||||||
|       set_presence_(false); |       this->set_presence_(false); | ||||||
|     } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') { |     } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') { | ||||||
|       set_presence_(true); |       this->set_presence_(true); | ||||||
|     } |     } | ||||||
|     if (inbuf[pos] >= '0' && inbuf[pos] <= '9') { |     if (inbuf[pos] >= '0' && inbuf[pos] <= '9') { | ||||||
|       if (index < bufsize - 1) { |       if (index < bufsize - 1) { | ||||||
| @@ -411,18 +408,18 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { | |||||||
|   } |   } | ||||||
|   outbuf[index] = '\0'; |   outbuf[index] = '\0'; | ||||||
|   if (index > 1) |   if (index > 1) | ||||||
|     set_distance_(strtol(outbuf, &endptr, 10)); |     this->set_distance_(strtol(outbuf, &endptr, 10)); | ||||||
|  |  | ||||||
|   if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { |   if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { | ||||||
|     // Resonable refresh rate for home assistant database size health |     // Resonable refresh rate for home assistant database size health | ||||||
|     const int32_t current_millis = millis(); |     const int32_t current_millis = App.get_loop_component_start_time(); | ||||||
|     if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) |     if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) | ||||||
|       return; |       return; | ||||||
|     this->last_normal_periodic_millis = current_millis; |     this->last_normal_periodic_millis = current_millis; | ||||||
|     for (auto &listener : this->listeners_) |     for (auto &listener : this->listeners_) | ||||||
|       listener->on_distance(get_distance_()); |       listener->on_distance(this->get_distance_()); | ||||||
|     for (auto &listener : this->listeners_) |     for (auto &listener : this->listeners_) | ||||||
|       listener->on_presence(get_presence_()); |       listener->on_presence(this->get_presence_()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -433,10 +430,10 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|   uint8_t data_element = 0; |   uint8_t data_element = 0; | ||||||
|   uint16_t data_pos = 0; |   uint16_t data_pos = 0; | ||||||
|   if (this->cmd_reply_.length > CMD_MAX_BYTES) { |   if (this->cmd_reply_.length > CMD_MAX_BYTES) { | ||||||
|     ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES); |     ESP_LOGW(TAG, "Reply frame too long"); | ||||||
|     return; |     return; | ||||||
|   } else if (this->cmd_reply_.length < 2) { |   } else if (this->cmd_reply_.length < 2) { | ||||||
|     ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes."); |     ESP_LOGW(TAG, "Command frame too short"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error)); |   memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error)); | ||||||
| @@ -447,13 +444,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|   this->cmd_reply_.ack = true; |   this->cmd_reply_.ack = true; | ||||||
|   switch ((uint16_t) this->cmd_reply_.command) { |   switch ((uint16_t) this->cmd_reply_.command) { | ||||||
|     case (CMD_ENABLE_CONF): |     case (CMD_ENABLE_CONF): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); |       ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_DISABLE_CONF): |     case (CMD_DISABLE_CONF): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); |       ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_READ_REGISTER): |     case (CMD_READ_REGISTER): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result); |       ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result); | ||||||
|       // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file |       // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file | ||||||
|       data_pos = 0x0A; |       data_pos = 0x0A; | ||||||
|       for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE *  // NOLINT |       for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE *  // NOLINT | ||||||
| @@ -465,13 +462,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case (CMD_WRITE_REGISTER): |     case (CMD_WRITE_REGISTER): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); |       ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_WRITE_ABD_PARAM): |     case (CMD_WRITE_ABD_PARAM): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); |       ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_READ_ABD_PARAM): |     case (CMD_READ_ABD_PARAM): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); |       ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); | ||||||
|       data_pos = CMD_ABD_DATA_REPLY_START; |       data_pos = CMD_ABD_DATA_REPLY_START; | ||||||
|       for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE *  // NOLINT |       for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE *  // NOLINT | ||||||
|                                         ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE)); |                                         ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE)); | ||||||
| @@ -483,11 +480,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case (CMD_WRITE_SYS_PARAM): |     case (CMD_WRITE_SYS_PARAM): | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); |       ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); | ||||||
|       break; |       break; | ||||||
|     case (CMD_READ_VERSION): |     case (CMD_READ_VERSION): | ||||||
|       memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]); |       memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]); | ||||||
|       ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result); |       ESP_LOGV(TAG, "Firmware version: %7s %s", this->ld2420_firmware_ver_, result); | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
| @@ -533,7 +530,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     while (!this->cmd_reply_.ack) { |     while (!this->cmd_reply_.ack) { | ||||||
|       while (available()) { |       while (this->available()) { | ||||||
|         this->readline_(read(), ack_buffer, sizeof(ack_buffer)); |         this->readline_(read(), ack_buffer, sizeof(ack_buffer)); | ||||||
|       } |       } | ||||||
|       delay_microseconds_safe(1450); |       delay_microseconds_safe(1450); | ||||||
| @@ -548,7 +545,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { | |||||||
|     if (this->cmd_reply_.ack) |     if (this->cmd_reply_.ack) | ||||||
|       retry = 0; |       retry = 0; | ||||||
|     if (this->cmd_reply_.error > 0) |     if (this->cmd_reply_.error > 0) | ||||||
|       handle_cmd_error(error); |       this->handle_cmd_error(error); | ||||||
|   } |   } | ||||||
|   return error; |   return error; | ||||||
| } | } | ||||||
| @@ -563,7 +560,7 @@ uint8_t LD2420Component::set_config_mode(bool enable) { | |||||||
|     cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER); |     cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER); | ||||||
|   } |   } | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); |   ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); | ||||||
|   return this->send_cmd_from_array(cmd_frame); |   return this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -576,7 +573,7 @@ void LD2420Component::ld2420_restart() { | |||||||
|   cmd_frame.header = CMD_FRAME_HEADER; |   cmd_frame.header = CMD_FRAME_HEADER; | ||||||
|   cmd_frame.command = CMD_RESTART; |   cmd_frame.command = CMD_RESTART; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -588,7 +585,7 @@ void LD2420Component::get_reg_value_(uint16_t reg) { | |||||||
|   cmd_frame.data[1] = reg; |   cmd_frame.data[1] = reg; | ||||||
|   cmd_frame.data_length += 2; |   cmd_frame.data_length += 2; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); |   ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -602,11 +599,11 @@ void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) { | |||||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE)); |   memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE)); | ||||||
|   cmd_frame.data_length += 2; |   cmd_frame.data_length += 2; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); |   ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); } | void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); } | ||||||
|  |  | ||||||
| int LD2420Component::get_gate_threshold_(uint8_t gate) { | int LD2420Component::get_gate_threshold_(uint8_t gate) { | ||||||
|   uint8_t error; |   uint8_t error; | ||||||
| @@ -619,7 +616,7 @@ int LD2420Component::get_gate_threshold_(uint8_t gate) { | |||||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate])); |   memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate])); | ||||||
|   cmd_frame.data_length += 2; |   cmd_frame.data_length += 2; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command); |   ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command); | ||||||
|   error = this->send_cmd_from_array(cmd_frame); |   error = this->send_cmd_from_array(cmd_frame); | ||||||
|   if (error == 0) { |   if (error == 0) { | ||||||
|     this->current_config.move_thresh[gate] = cmd_reply_.data[0]; |     this->current_config.move_thresh[gate] = cmd_reply_.data[0]; | ||||||
| @@ -644,7 +641,7 @@ int LD2420Component::get_min_max_distances_timeout_() { | |||||||
|          sizeof(CMD_TIMEOUT_REG));  // Register: global delay time |          sizeof(CMD_TIMEOUT_REG));  // Register: global delay time | ||||||
|   cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); |   cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); | ||||||
|   error = this->send_cmd_from_array(cmd_frame); |   error = this->send_cmd_from_array(cmd_frame); | ||||||
|   if (error == 0) { |   if (error == 0) { | ||||||
|     this->current_config.min_gate = (uint16_t) cmd_reply_.data[0]; |     this->current_config.min_gate = (uint16_t) cmd_reply_.data[0]; | ||||||
| @@ -667,9 +664,9 @@ void LD2420Component::set_system_mode(uint16_t mode) { | |||||||
|   memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm)); |   memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm)); | ||||||
|   cmd_frame.data_length += sizeof(unknown_parm); |   cmd_frame.data_length += sizeof(unknown_parm); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command); | ||||||
|   if (this->send_cmd_from_array(cmd_frame) == 0) |   if (this->send_cmd_from_array(cmd_frame) == 0) | ||||||
|     set_mode_(mode); |     this->set_mode_(mode); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2420Component::get_firmware_version_() { | void LD2420Component::get_firmware_version_() { | ||||||
| @@ -679,7 +676,7 @@ void LD2420Component::get_firmware_version_() { | |||||||
|   cmd_frame.command = CMD_READ_VERSION; |   cmd_frame.command = CMD_READ_VERSION; | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -712,7 +709,7 @@ void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, | |||||||
|   cmd_frame.data_length += sizeof(timeout); |   cmd_frame.data_length += sizeof(timeout); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); |   ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -738,7 +735,7 @@ void LD2420Component::set_gate_threshold(uint8_t gate) { | |||||||
|          sizeof(this->new_config.still_thresh[gate])); |          sizeof(this->new_config.still_thresh[gate])); | ||||||
|   cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]); |   cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]); | ||||||
|   cmd_frame.footer = CMD_FRAME_FOOTER; |   cmd_frame.footer = CMD_FRAME_FOOTER; | ||||||
|   ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); |   ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); | ||||||
|   this->send_cmd_from_array(cmd_frame); |   this->send_cmd_from_array(cmd_frame); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -179,7 +179,7 @@ class LD2420Component : public Component, public uart::UARTDevice { | |||||||
|   void set_operating_mode(const std::string &state); |   void set_operating_mode(const std::string &state); | ||||||
|   void auto_calibrate_sensitivity(); |   void auto_calibrate_sensitivity(); | ||||||
|   void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); |   void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); | ||||||
|   uint8_t calc_checksum(void *data, size_t size); |   static uint8_t calc_checksum(void *data, size_t size); | ||||||
|  |  | ||||||
|   RegConfigT current_config; |   RegConfigT current_config; | ||||||
|   RegConfigT new_config; |   RegConfigT new_config; | ||||||
| @@ -222,7 +222,7 @@ class LD2420Component : public Component, public uart::UARTDevice { | |||||||
|     volatile bool ack; |     volatile bool ack; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   int get_firmware_int_(const char *version_string); |   static int get_firmware_int(const char *version_string); | ||||||
|   void get_firmware_version_(); |   void get_firmware_version_(); | ||||||
|   int get_gate_threshold_(uint8_t gate); |   int get_gate_threshold_(uint8_t gate); | ||||||
|   void get_reg_value_(uint16_t reg); |   void get_reg_value_(uint16_t reg); | ||||||
|   | |||||||
| @@ -6,7 +6,9 @@ | |||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #endif | #endif | ||||||
|  | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #define highbyte(val) (uint8_t)((val) >> 8) | #define highbyte(val) (uint8_t)((val) >> 8) | ||||||
| #define lowbyte(val) (uint8_t)((val) &0xff) | #define lowbyte(val) (uint8_t)((val) &0xff) | ||||||
| @@ -15,23 +17,109 @@ namespace esphome { | |||||||
| namespace ld2450 { | namespace ld2450 { | ||||||
|  |  | ||||||
| static const char *const TAG = "ld2450"; | static const char *const TAG = "ld2450"; | ||||||
| static const char *const NO_MAC("08:05:04:03:02:01"); | static const char *const NO_MAC = "08:05:04:03:02:01"; | ||||||
| static const char *const UNKNOWN_MAC("unknown"); | static const char *const UNKNOWN_MAC = "unknown"; | ||||||
|  | static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; | ||||||
|  |  | ||||||
|  | enum BaudRateStructure : uint8_t { | ||||||
|  |   BAUD_RATE_9600 = 1, | ||||||
|  |   BAUD_RATE_19200 = 2, | ||||||
|  |   BAUD_RATE_38400 = 3, | ||||||
|  |   BAUD_RATE_57600 = 4, | ||||||
|  |   BAUD_RATE_115200 = 5, | ||||||
|  |   BAUD_RATE_230400 = 6, | ||||||
|  |   BAUD_RATE_256000 = 7, | ||||||
|  |   BAUD_RATE_460800 = 8 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Zone type struct | ||||||
|  | enum ZoneTypeStructure : uint8_t { | ||||||
|  |   ZONE_DISABLED = 0, | ||||||
|  |   ZONE_DETECTION = 1, | ||||||
|  |   ZONE_FILTER = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum PeriodicDataStructure : uint8_t { | ||||||
|  |   TARGET_X = 4, | ||||||
|  |   TARGET_Y = 6, | ||||||
|  |   TARGET_SPEED = 8, | ||||||
|  |   TARGET_RESOLUTION = 10, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum PeriodicDataValue : uint8_t { | ||||||
|  |   HEAD = 0xAA, | ||||||
|  |   END = 0x55, | ||||||
|  |   CHECK = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum AckDataStructure : uint8_t { | ||||||
|  |   COMMAND = 6, | ||||||
|  |   COMMAND_STATUS = 7, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Memory-efficient lookup tables | ||||||
|  | struct StringToUint8 { | ||||||
|  |   const char *str; | ||||||
|  |   uint8_t value; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct Uint8ToString { | ||||||
|  |   uint8_t value; | ||||||
|  |   const char *str; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr StringToUint8 BAUD_RATES_BY_STR[] = { | ||||||
|  |     {"9600", BAUD_RATE_9600},     {"19200", BAUD_RATE_19200},   {"38400", BAUD_RATE_38400}, | ||||||
|  |     {"57600", BAUD_RATE_57600},   {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400}, | ||||||
|  |     {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr Uint8ToString ZONE_TYPE_BY_UINT[] = { | ||||||
|  |     {ZONE_DISABLED, "Disabled"}, | ||||||
|  |     {ZONE_DETECTION, "Detection"}, | ||||||
|  |     {ZONE_FILTER, "Filter"}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | constexpr StringToUint8 ZONE_TYPE_BY_STR[] = { | ||||||
|  |     {"Disabled", ZONE_DISABLED}, | ||||||
|  |     {"Detection", ZONE_DETECTION}, | ||||||
|  |     {"Filter", ZONE_FILTER}, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Helper functions for lookups | ||||||
|  | template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { | ||||||
|  |   for (const auto &entry : arr) { | ||||||
|  |     if (str == entry.str) | ||||||
|  |       return entry.value; | ||||||
|  |   } | ||||||
|  |   return 0xFF;  // Not found | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) { | ||||||
|  |   for (const auto &entry : arr) { | ||||||
|  |     if (value == entry.value) | ||||||
|  |       return entry.str; | ||||||
|  |   } | ||||||
|  |   return "";  // Not found | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LD2450 serial command header & footer | ||||||
|  | static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA}; | ||||||
|  | static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01}; | ||||||
| // LD2450 UART Serial Commands | // LD2450 UART Serial Commands | ||||||
| static const uint8_t CMD_ENABLE_CONF = 0x00FF; | static const uint8_t CMD_ENABLE_CONF = 0xFF; | ||||||
| static const uint8_t CMD_DISABLE_CONF = 0x00FE; | static const uint8_t CMD_DISABLE_CONF = 0xFE; | ||||||
| static const uint8_t CMD_VERSION = 0x00A0; | static const uint8_t CMD_VERSION = 0xA0; | ||||||
| static const uint8_t CMD_MAC = 0x00A5; | static const uint8_t CMD_MAC = 0xA5; | ||||||
| static const uint8_t CMD_RESET = 0x00A2; | static const uint8_t CMD_RESET = 0xA2; | ||||||
| static const uint8_t CMD_RESTART = 0x00A3; | static const uint8_t CMD_RESTART = 0xA3; | ||||||
| static const uint8_t CMD_BLUETOOTH = 0x00A4; | static const uint8_t CMD_BLUETOOTH = 0xA4; | ||||||
| static const uint8_t CMD_SINGLE_TARGET_MODE = 0x0080; | static const uint8_t CMD_SINGLE_TARGET_MODE = 0x80; | ||||||
| static const uint8_t CMD_MULTI_TARGET_MODE = 0x0090; | static const uint8_t CMD_MULTI_TARGET_MODE = 0x90; | ||||||
| static const uint8_t CMD_QUERY_TARGET_MODE = 0x0091; | static const uint8_t CMD_QUERY_TARGET_MODE = 0x91; | ||||||
| static const uint8_t CMD_SET_BAUD_RATE = 0x00A1; | static const uint8_t CMD_SET_BAUD_RATE = 0xA1; | ||||||
| static const uint8_t CMD_QUERY_ZONE = 0x00C1; | static const uint8_t CMD_QUERY_ZONE = 0xC1; | ||||||
| static const uint8_t CMD_SET_ZONE = 0x00C2; | static const uint8_t CMD_SET_ZONE = 0xC2; | ||||||
|  |  | ||||||
| static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; }; | static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; }; | ||||||
|  |  | ||||||
| @@ -96,18 +184,6 @@ static inline std::string get_direction(int16_t speed) { | |||||||
|   return STATIONARY; |   return STATIONARY; | ||||||
| } | } | ||||||
|  |  | ||||||
| static inline std::string format_mac(uint8_t *buffer) { |  | ||||||
|   return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], |  | ||||||
|                       buffer[15]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static inline std::string format_version(uint8_t *buffer) { |  | ||||||
|   return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], |  | ||||||
|                      buffer[14]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| LD2450Component::LD2450Component() {} |  | ||||||
|  |  | ||||||
| void LD2450Component::setup() { | void LD2450Component::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Running setup"); |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| @@ -120,7 +196,7 @@ void LD2450Component::setup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void LD2450Component::dump_config() { | void LD2450Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:"); |   ESP_LOGCONFIG(TAG, "LD2450:"); | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   LOG_BINARY_SENSOR("  ", "TargetBinarySensor", this->target_binary_sensor_); |   LOG_BINARY_SENSOR("  ", "TargetBinarySensor", this->target_binary_sensor_); | ||||||
|   LOG_BINARY_SENSOR("  ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); |   LOG_BINARY_SENSOR("  ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); | ||||||
| @@ -189,10 +265,10 @@ void LD2450Component::dump_config() { | |||||||
|   LOG_NUMBER("  ", "PresenceTimeoutNumber", this->presence_timeout_number_); |   LOG_NUMBER("  ", "PresenceTimeoutNumber", this->presence_timeout_number_); | ||||||
| #endif | #endif | ||||||
|   ESP_LOGCONFIG(TAG, |   ESP_LOGCONFIG(TAG, | ||||||
|                 "  Throttle : %ums\n" |                 "  Throttle: %ums\n" | ||||||
|                 "  MAC Address : %s\n" |                 "  MAC Address: %s\n" | ||||||
|                 "  Firmware version : %s", |                 "  Firmware version: %s", | ||||||
|                 this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str())); |                 this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void LD2450Component::loop() { | void LD2450Component::loop() { | ||||||
| @@ -266,8 +342,7 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) { | |||||||
|   if (this->timeout_ == 0) { |   if (this->timeout_ == 0) { | ||||||
|     this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT); |     this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT); | ||||||
|   } |   } | ||||||
|   auto current_millis = millis(); |   return App.get_loop_component_start_time() - check_millis >= this->timeout_; | ||||||
|   return current_millis - check_millis >= this->timeout_; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Extract, store and publish zone details LD2450 buffer | // Extract, store and publish zone details LD2450 buffer | ||||||
| @@ -354,25 +429,24 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu | |||||||
| //   Header       Target 1                  Target 2                  Target 3                  End | //   Header       Target 1                  Target 2                  Target 3                  End | ||||||
| void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | ||||||
|   if (len < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes) |   if (len < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes) | ||||||
|     ESP_LOGE(TAG, "Periodic data: invalid message length"); |     ESP_LOGE(TAG, "Invalid message length"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) {  // header |   if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) {  // header | ||||||
|     ESP_LOGE(TAG, "Periodic data: invalid message header"); |     ESP_LOGE(TAG, "Invalid message header"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) {  // footer |   if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) {  // footer | ||||||
|     ESP_LOGE(TAG, "Periodic data: invalid message footer"); |     ESP_LOGE(TAG, "Invalid message footer"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   auto current_millis = millis(); |   if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { | ||||||
|   if (current_millis - this->last_periodic_millis_ < this->throttle_) { |  | ||||||
|     ESP_LOGV(TAG, "Throttling: %d", this->throttle_); |     ESP_LOGV(TAG, "Throttling: %d", this->throttle_); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->last_periodic_millis_ = current_millis; |   this->last_periodic_millis_ = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|   int16_t target_count = 0; |   int16_t target_count = 0; | ||||||
|   int16_t still_target_count = 0; |   int16_t still_target_count = 0; | ||||||
| @@ -555,13 +629,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|   // For presence timeout check |   // For presence timeout check | ||||||
|   if (target_count > 0) { |   if (target_count > 0) { | ||||||
|     this->presence_millis_ = millis(); |     this->presence_millis_ = App.get_loop_component_start_time(); | ||||||
|   } |   } | ||||||
|   if (moving_target_count > 0) { |   if (moving_target_count > 0) { | ||||||
|     this->moving_presence_millis_ = millis(); |     this->moving_presence_millis_ = App.get_loop_component_start_time(); | ||||||
|   } |   } | ||||||
|   if (still_target_count > 0) { |   if (still_target_count > 0) { | ||||||
|     this->still_presence_millis_ = millis(); |     this->still_presence_millis_ = App.get_loop_component_start_time(); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| @@ -569,31 +643,31 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
| bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | ||||||
|   ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); |   ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); | ||||||
|   if (len < 10) { |   if (len < 10) { | ||||||
|     ESP_LOGE(TAG, "Ack data: invalid length"); |     ESP_LOGE(TAG, "Invalid ack length"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // frame header |   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // frame header | ||||||
|     ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]); |     ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[COMMAND_STATUS] != 0x01) { |   if (buffer[COMMAND_STATUS] != 0x01) { | ||||||
|     ESP_LOGE(TAG, "Ack data: invalid status"); |     ESP_LOGE(TAG, "Invalid ack status"); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (buffer[8] || buffer[9]) { |   if (buffer[8] || buffer[9]) { | ||||||
|     ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]); |     ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   switch (buffer[COMMAND]) { |   switch (buffer[COMMAND]) { | ||||||
|     case lowbyte(CMD_ENABLE_CONF): |     case lowbyte(CMD_ENABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Got enable conf command"); |       ESP_LOGV(TAG, "Enable conf command"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_DISABLE_CONF): |     case lowbyte(CMD_DISABLE_CONF): | ||||||
|       ESP_LOGV(TAG, "Got disable conf command"); |       ESP_LOGV(TAG, "Disable conf command"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_BAUD_RATE): |     case lowbyte(CMD_SET_BAUD_RATE): | ||||||
|       ESP_LOGV(TAG, "Got baud rate change command"); |       ESP_LOGV(TAG, "Baud rate change command"); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|       if (this->baud_rate_select_ != nullptr) { |       if (this->baud_rate_select_ != nullptr) { | ||||||
|         ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); |         ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); | ||||||
| @@ -601,7 +675,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_VERSION): |     case lowbyte(CMD_VERSION): | ||||||
|       this->version_ = ld2450::format_version(buffer); |       this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]); | ||||||
|       ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str()); |       ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str()); | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|       if (this->version_text_sensor_ != nullptr) { |       if (this->version_text_sensor_ != nullptr) { | ||||||
| @@ -613,7 +687,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
|       if (len < 20) { |       if (len < 20) { | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       this->mac_ = ld2450::format_mac(buffer); |       this->mac_ = format_mac_address_pretty(&buffer[10]); | ||||||
|       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); |       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|       if (this->mac_text_sensor_ != nullptr) { |       if (this->mac_text_sensor_ != nullptr) { | ||||||
| @@ -627,10 +701,10 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_BLUETOOTH): |     case lowbyte(CMD_BLUETOOTH): | ||||||
|       ESP_LOGV(TAG, "Got Bluetooth command"); |       ESP_LOGV(TAG, "Bluetooth command"); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SINGLE_TARGET_MODE): |     case lowbyte(CMD_SINGLE_TARGET_MODE): | ||||||
|       ESP_LOGV(TAG, "Got single target conf command"); |       ESP_LOGV(TAG, "Single target conf command"); | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->multi_target_switch_ != nullptr) { |       if (this->multi_target_switch_ != nullptr) { | ||||||
|         this->multi_target_switch_->publish_state(false); |         this->multi_target_switch_->publish_state(false); | ||||||
| @@ -638,7 +712,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_MULTI_TARGET_MODE): |     case lowbyte(CMD_MULTI_TARGET_MODE): | ||||||
|       ESP_LOGV(TAG, "Got multi target conf command"); |       ESP_LOGV(TAG, "Multi target conf command"); | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->multi_target_switch_ != nullptr) { |       if (this->multi_target_switch_ != nullptr) { | ||||||
|         this->multi_target_switch_->publish_state(true); |         this->multi_target_switch_->publish_state(true); | ||||||
| @@ -646,7 +720,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_QUERY_TARGET_MODE): |     case lowbyte(CMD_QUERY_TARGET_MODE): | ||||||
|       ESP_LOGV(TAG, "Got query target tracking mode command"); |       ESP_LOGV(TAG, "Query target tracking mode command"); | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|       if (this->multi_target_switch_ != nullptr) { |       if (this->multi_target_switch_ != nullptr) { | ||||||
|         this->multi_target_switch_->publish_state(buffer[10] == 0x02); |         this->multi_target_switch_->publish_state(buffer[10] == 0x02); | ||||||
| @@ -654,7 +728,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_QUERY_ZONE): |     case lowbyte(CMD_QUERY_ZONE): | ||||||
|       ESP_LOGV(TAG, "Got query zone conf command"); |       ESP_LOGV(TAG, "Query zone conf command"); | ||||||
|       this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); |       this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); | ||||||
|       this->publish_zone_type(); |       this->publish_zone_type(); | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| @@ -674,7 +748,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | |||||||
|       this->process_zone_(buffer); |       this->process_zone_(buffer); | ||||||
|       break; |       break; | ||||||
|     case lowbyte(CMD_SET_ZONE): |     case lowbyte(CMD_SET_ZONE): | ||||||
|       ESP_LOGV(TAG, "Got set zone conf command"); |       ESP_LOGV(TAG, "Set zone conf command"); | ||||||
|       this->query_zone_info(); |       this->query_zone_info(); | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
| @@ -731,7 +805,7 @@ void LD2450Component::set_bluetooth(bool enable) { | |||||||
| // Set Baud rate | // Set Baud rate | ||||||
| void LD2450Component::set_baud_rate(const std::string &state) { | void LD2450Component::set_baud_rate(const std::string &state) { | ||||||
|   this->set_config_mode_(true); |   this->set_config_mode_(true); | ||||||
|   uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00}; |   uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; | ||||||
|   this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); |   this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); | ||||||
|   this->set_timeout(200, [this]() { this->restart_(); }); |   this->set_timeout(200, [this]() { this->restart_(); }); | ||||||
| } | } | ||||||
| @@ -739,7 +813,7 @@ void LD2450Component::set_baud_rate(const std::string &state) { | |||||||
| // Set Zone Type - one of: Disabled, Detection, Filter | // Set Zone Type - one of: Disabled, Detection, Filter | ||||||
| void LD2450Component::set_zone_type(const std::string &state) { | void LD2450Component::set_zone_type(const std::string &state) { | ||||||
|   ESP_LOGV(TAG, "Set zone type: %s", state.c_str()); |   ESP_LOGV(TAG, "Set zone type: %s", state.c_str()); | ||||||
|   uint8_t zone_type = ZONE_TYPE_ENUM_TO_INT.at(state); |   uint8_t zone_type = find_uint8(ZONE_TYPE_BY_STR, state); | ||||||
|   this->zone_type_ = zone_type; |   this->zone_type_ = zone_type; | ||||||
|   this->send_set_zone_command_(); |   this->send_set_zone_command_(); | ||||||
| } | } | ||||||
| @@ -747,7 +821,7 @@ void LD2450Component::set_zone_type(const std::string &state) { | |||||||
| // Publish Zone Type to Select component | // Publish Zone Type to Select component | ||||||
| void LD2450Component::publish_zone_type() { | void LD2450Component::publish_zone_type() { | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   std::string zone_type = ZONE_TYPE_INT_TO_ENUM.at(static_cast<ZoneTypeStructure>(this->zone_type_)); |   std::string zone_type = find_str(ZONE_TYPE_BY_UINT, this->zone_type_); | ||||||
|   if (this->zone_type_select_ != nullptr) { |   if (this->zone_type_select_ != nullptr) { | ||||||
|     this->zone_type_select_->publish_state(zone_type); |     this->zone_type_select_->publish_state(zone_type); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <iomanip> |  | ||||||
| #include <map> |  | ||||||
| #include "esphome/components/uart/uart.h" | #include "esphome/components/uart/uart.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| @@ -66,49 +64,6 @@ struct ZoneOfNumbers { | |||||||
| }; | }; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| enum BaudRateStructure : uint8_t { |  | ||||||
|   BAUD_RATE_9600 = 1, |  | ||||||
|   BAUD_RATE_19200 = 2, |  | ||||||
|   BAUD_RATE_38400 = 3, |  | ||||||
|   BAUD_RATE_57600 = 4, |  | ||||||
|   BAUD_RATE_115200 = 5, |  | ||||||
|   BAUD_RATE_230400 = 6, |  | ||||||
|   BAUD_RATE_256000 = 7, |  | ||||||
|   BAUD_RATE_460800 = 8 |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // Convert baud rate enum to int |  | ||||||
| static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{ |  | ||||||
|     {"9600", BAUD_RATE_9600},     {"19200", BAUD_RATE_19200},   {"38400", BAUD_RATE_38400}, |  | ||||||
|     {"57600", BAUD_RATE_57600},   {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400}, |  | ||||||
|     {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}}; |  | ||||||
|  |  | ||||||
| // Zone type struct |  | ||||||
| enum ZoneTypeStructure : uint8_t { ZONE_DISABLED = 0, ZONE_DETECTION = 1, ZONE_FILTER = 2 }; |  | ||||||
|  |  | ||||||
| // Convert zone type int to enum |  | ||||||
| static const std::map<ZoneTypeStructure, std::string> ZONE_TYPE_INT_TO_ENUM{ |  | ||||||
|     {ZONE_DISABLED, "Disabled"}, {ZONE_DETECTION, "Detection"}, {ZONE_FILTER, "Filter"}}; |  | ||||||
|  |  | ||||||
| // Convert zone type enum to int |  | ||||||
| static const std::map<std::string, uint8_t> ZONE_TYPE_ENUM_TO_INT{ |  | ||||||
|     {"Disabled", ZONE_DISABLED}, {"Detection", ZONE_DETECTION}, {"Filter", ZONE_FILTER}}; |  | ||||||
|  |  | ||||||
| // LD2450 serial command header & footer |  | ||||||
| static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA}; |  | ||||||
| static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01}; |  | ||||||
|  |  | ||||||
| enum PeriodicDataStructure : uint8_t { |  | ||||||
|   TARGET_X = 4, |  | ||||||
|   TARGET_Y = 6, |  | ||||||
|   TARGET_SPEED = 8, |  | ||||||
|   TARGET_RESOLUTION = 10, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum PeriodicDataValue : uint8_t { HEAD = 0xAA, END = 0x55, CHECK = 0x00 }; |  | ||||||
|  |  | ||||||
| enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 }; |  | ||||||
|  |  | ||||||
| class LD2450Component : public Component, public uart::UARTDevice { | class LD2450Component : public Component, public uart::UARTDevice { | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|   SUB_SENSOR(target_count) |   SUB_SENSOR(target_count) | ||||||
| @@ -141,7 +96,6 @@ class LD2450Component : public Component, public uart::UARTDevice { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   LD2450Component(); |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
| @@ -197,17 +151,17 @@ class LD2450Component : public Component, public uart::UARTDevice { | |||||||
|   bool get_timeout_status_(uint32_t check_millis); |   bool get_timeout_status_(uint32_t check_millis); | ||||||
|   uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving); |   uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving); | ||||||
|  |  | ||||||
|   Target target_info_[MAX_TARGETS]; |  | ||||||
|   Zone zone_config_[MAX_ZONES]; |  | ||||||
|   uint8_t buffer_pos_ = 0;  // where to resume processing/populating buffer |  | ||||||
|   uint8_t buffer_data_[MAX_LINE_LENGTH]; |  | ||||||
|   uint32_t last_periodic_millis_ = 0; |   uint32_t last_periodic_millis_ = 0; | ||||||
|   uint32_t presence_millis_ = 0; |   uint32_t presence_millis_ = 0; | ||||||
|   uint32_t still_presence_millis_ = 0; |   uint32_t still_presence_millis_ = 0; | ||||||
|   uint32_t moving_presence_millis_ = 0; |   uint32_t moving_presence_millis_ = 0; | ||||||
|   uint16_t throttle_ = 0; |   uint16_t throttle_ = 0; | ||||||
|   uint16_t timeout_ = 5; |   uint16_t timeout_ = 5; | ||||||
|  |   uint8_t buffer_pos_ = 0;  // where to resume processing/populating buffer | ||||||
|  |   uint8_t buffer_data_[MAX_LINE_LENGTH]; | ||||||
|   uint8_t zone_type_ = 0; |   uint8_t zone_type_ = 0; | ||||||
|  |   Target target_info_[MAX_TARGETS]; | ||||||
|  |   Zone zone_config_[MAX_ZONES]; | ||||||
|   std::string version_{}; |   std::string version_{}; | ||||||
|   std::string mac_{}; |   std::string mac_{}; | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|   | |||||||
| @@ -38,8 +38,8 @@ from esphome.const import ( | |||||||
|     CONF_WHITE, |     CONF_WHITE, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| from .automation import LIGHT_STATE_SCHEMA | from .automation import LIGHT_STATE_SCHEMA | ||||||
| from .effects import ( | from .effects import ( | ||||||
| @@ -110,6 +110,8 @@ LIGHT_SCHEMA = ( | |||||||
|     ) |     ) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | LIGHT_SCHEMA.add_extra(entity_duplicate_validator("light")) | ||||||
|  |  | ||||||
| BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( | BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend( | ||||||
|     { |     { | ||||||
|         cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS), |         cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS), | ||||||
| @@ -207,7 +209,7 @@ def validate_color_temperature_channels(value): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_light_core_(light_var, output_var, config): | async def setup_light_core_(light_var, output_var, config): | ||||||
|     await setup_entity(light_var, config) |     await setup_entity(light_var, config, "light") | ||||||
|  |  | ||||||
|     cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) |     cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,8 +14,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -67,6 +67,9 @@ _LOCK_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _LOCK_SCHEMA.add_extra(entity_duplicate_validator("lock")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def lock_schema( | def lock_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
|     *, |     *, | ||||||
| @@ -94,7 +97,7 @@ LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def _setup_lock_core(var, config): | async def _setup_lock_core(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "lock") | ||||||
|  |  | ||||||
|     for conf in config.get(CONF_ON_LOCK, []): |     for conf in config.get(CONF_ON_LOCK, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -184,7 +184,9 @@ CONFIG_SCHEMA = cv.All( | |||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(Logger), |             cv.GenerateID(): cv.declare_id(Logger), | ||||||
|             cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, |             cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int, | ||||||
|             cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes, |             cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.All( | ||||||
|  |                 cv.validate_bytes, cv.int_range(min=160, max=65535) | ||||||
|  |             ), | ||||||
|             cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, |             cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean, | ||||||
|             cv.SplitDefault( |             cv.SplitDefault( | ||||||
|                 CONF_TASK_LOG_BUFFER_SIZE, |                 CONF_TASK_LOG_BUFFER_SIZE, | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ static const char *const TAG = "logger"; | |||||||
| //    - Messages are serialized through main loop for proper console output | //    - Messages are serialized through main loop for proper console output | ||||||
| //    - Fallback to emergency console logging only if ring buffer is full | //    - Fallback to emergency console logging only if ring buffer is full | ||||||
| //  - WITHOUT task log buffer: Only emergency console output, no callbacks | //  - WITHOUT task log buffer: Only emergency console output, no callbacks | ||||||
| void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) {  // NOLINT | void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) {  // NOLINT | ||||||
|   if (level > this->level_for(tag)) |   if (level > this->level_for(tag)) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
| @@ -46,8 +46,13 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * | |||||||
|   bool message_sent = false; |   bool message_sent = false; | ||||||
| #ifdef USE_ESPHOME_TASK_LOG_BUFFER | #ifdef USE_ESPHOME_TASK_LOG_BUFFER | ||||||
|   // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered |   // For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered | ||||||
|   message_sent = this->log_buffer_->send_message_thread_safe(static_cast<uint8_t>(level), tag, |   message_sent = | ||||||
|                                                              static_cast<uint16_t>(line), current_task, format, args); |       this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args); | ||||||
|  |   if (message_sent) { | ||||||
|  |     // Enable logger loop to process the buffered message | ||||||
|  |     // This is safe to call from any context including ISRs | ||||||
|  |     this->enable_loop_soon_any_context(); | ||||||
|  |   } | ||||||
| #endif  // USE_ESPHOME_TASK_LOG_BUFFER | #endif  // USE_ESPHOME_TASK_LOG_BUFFER | ||||||
|  |  | ||||||
|   // Emergency console logging for non-main tasks when ring buffer is full or disabled |   // Emergency console logging for non-main tasks when ring buffer is full or disabled | ||||||
| @@ -58,7 +63,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * | |||||||
|     // Maximum size for console log messages (includes null terminator) |     // Maximum size for console log messages (includes null terminator) | ||||||
|     static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; |     static const size_t MAX_CONSOLE_LOG_MSG_SIZE = 144; | ||||||
|     char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE];  // MUST be stack allocated for thread safety |     char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE];  // MUST be stack allocated for thread safety | ||||||
|     int buffer_at = 0;                              // Initialize buffer position |     uint16_t buffer_at = 0;                         // Initialize buffer position | ||||||
|     this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, |     this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at, | ||||||
|                                                 MAX_CONSOLE_LOG_MSG_SIZE); |                                                 MAX_CONSOLE_LOG_MSG_SIZE); | ||||||
|     this->write_msg_(console_buffer); |     this->write_msg_(console_buffer); | ||||||
| @@ -69,7 +74,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * | |||||||
| } | } | ||||||
| #else | #else | ||||||
| // Implementation for all other platforms | // Implementation for all other platforms | ||||||
| void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) {  // NOLINT | void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) {  // NOLINT | ||||||
|   if (level > this->level_for(tag) || global_recursion_guard_) |   if (level > this->level_for(tag) || global_recursion_guard_) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
| @@ -85,7 +90,7 @@ void HOT Logger::log_vprintf_(int level, const char *tag, int line, const char * | |||||||
| #ifdef USE_STORE_LOG_STR_IN_FLASH | #ifdef USE_STORE_LOG_STR_IN_FLASH | ||||||
| // Implementation for ESP8266 with flash string support. | // Implementation for ESP8266 with flash string support. | ||||||
| // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. | // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. | ||||||
| void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, | void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, | ||||||
|                           va_list args) {  // NOLINT |                           va_list args) {  // NOLINT | ||||||
|   if (level > this->level_for(tag) || global_recursion_guard_) |   if (level > this->level_for(tag) || global_recursion_guard_) | ||||||
|     return; |     return; | ||||||
| @@ -122,7 +127,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr | |||||||
| } | } | ||||||
| #endif  // USE_STORE_LOG_STR_IN_FLASH | #endif  // USE_STORE_LOG_STR_IN_FLASH | ||||||
|  |  | ||||||
| inline int Logger::level_for(const char *tag) { | inline uint8_t Logger::level_for(const char *tag) { | ||||||
|   auto it = this->log_levels_.find(tag); |   auto it = this->log_levels_.find(tag); | ||||||
|   if (it != this->log_levels_.end()) |   if (it != this->log_levels_.end()) | ||||||
|     return it->second; |     return it->second; | ||||||
| @@ -139,6 +144,10 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate | |||||||
| #ifdef USE_ESPHOME_TASK_LOG_BUFFER | #ifdef USE_ESPHOME_TASK_LOG_BUFFER | ||||||
| void Logger::init_log_buffer(size_t total_buffer_size) { | void Logger::init_log_buffer(size_t total_buffer_size) { | ||||||
|   this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size); |   this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size); | ||||||
|  |  | ||||||
|  |   // Start with loop disabled when using task buffer (unless using USB CDC) | ||||||
|  |   // The loop will be enabled automatically when messages arrive | ||||||
|  |   this->disable_loop_when_buffer_empty_(); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -189,19 +198,23 @@ void Logger::loop() { | |||||||
|         this->write_msg_(this->tx_buffer_); |         this->write_msg_(this->tx_buffer_); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |   } else { | ||||||
|  |     // No messages to process, disable loop if appropriate | ||||||
|  |     // This reduces overhead when there's no async logging activity | ||||||
|  |     this->disable_loop_when_buffer_empty_(); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } | void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } | ||||||
| void Logger::set_log_level(const std::string &tag, int log_level) { this->log_levels_[tag] = log_level; } | void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; } | ||||||
|  |  | ||||||
| #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) | ||||||
| UARTSelection Logger::get_uart() const { return this->uart_; } | UARTSelection Logger::get_uart() const { return this->uart_; } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) { | void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback) { | ||||||
|   this->log_callback_.add(std::move(callback)); |   this->log_callback_.add(std::move(callback)); | ||||||
| } | } | ||||||
| float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; } | float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; } | ||||||
| @@ -230,7 +243,7 @@ void Logger::dump_config() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void Logger::set_log_level(int level) { | void Logger::set_log_level(uint8_t level) { | ||||||
|   if (level > ESPHOME_LOG_LEVEL) { |   if (level > ESPHOME_LOG_LEVEL) { | ||||||
|     level = ESPHOME_LOG_LEVEL; |     level = ESPHOME_LOG_LEVEL; | ||||||
|     ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); |     ESP_LOGW(TAG, "Cannot set log level higher than pre-compiled %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ static const char *const LOG_LEVEL_LETTERS[] = { | |||||||
|  * |  * | ||||||
|  * Advanced configuration (pin selection, etc) is not supported. |  * Advanced configuration (pin selection, etc) is not supported. | ||||||
|  */ |  */ | ||||||
| enum UARTSelection { | enum UARTSelection : uint8_t { | ||||||
| #ifdef USE_LIBRETINY | #ifdef USE_LIBRETINY | ||||||
|   UART_SELECTION_DEFAULT = 0, |   UART_SELECTION_DEFAULT = 0, | ||||||
|   UART_SELECTION_UART0, |   UART_SELECTION_UART0, | ||||||
| @@ -129,10 +129,10 @@ class Logger : public Component { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   /// Set the default log level for this logger. |   /// Set the default log level for this logger. | ||||||
|   void set_log_level(int level); |   void set_log_level(uint8_t level); | ||||||
|   /// Set the log level of the specified tag. |   /// Set the log level of the specified tag. | ||||||
|   void set_log_level(const std::string &tag, int log_level); |   void set_log_level(const std::string &tag, uint8_t log_level); | ||||||
|   int get_log_level() { return this->current_level_; } |   uint8_t get_log_level() { return this->current_level_; } | ||||||
|  |  | ||||||
|   // ========== INTERNAL METHODS ========== |   // ========== INTERNAL METHODS ========== | ||||||
|   // (In most use cases you won't need these) |   // (In most use cases you won't need these) | ||||||
| @@ -140,19 +140,20 @@ class Logger : public Component { | |||||||
|   void pre_setup(); |   void pre_setup(); | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|   inline int level_for(const char *tag); |   inline uint8_t level_for(const char *tag); | ||||||
|  |  | ||||||
|   /// Register a callback that will be called for every log message sent |   /// Register a callback that will be called for every log message sent | ||||||
|   void add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback); |   void add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback); | ||||||
|  |  | ||||||
|   // add a listener for log level changes |   // add a listener for log level changes | ||||||
|   void add_listener(std::function<void(int)> &&callback) { this->level_callback_.add(std::move(callback)); } |   void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); } | ||||||
|  |  | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|   void log_vprintf_(int level, const char *tag, int line, const char *format, va_list args);  // NOLINT |   void log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args);  // NOLINT | ||||||
| #ifdef USE_STORE_LOG_STR_IN_FLASH | #ifdef USE_STORE_LOG_STR_IN_FLASH | ||||||
|   void log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args);  // NOLINT |   void log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, | ||||||
|  |                     va_list args);  // NOLINT | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| @@ -160,8 +161,9 @@ class Logger : public Component { | |||||||
|  |  | ||||||
|   // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator |   // Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator | ||||||
|   // It's the caller's responsibility to initialize buffer_at (typically to 0) |   // It's the caller's responsibility to initialize buffer_at (typically to 0) | ||||||
|   inline void HOT format_log_to_buffer_with_terminator_(int level, const char *tag, int line, const char *format, |   inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format, | ||||||
|                                                         va_list args, char *buffer, int *buffer_at, int buffer_size) { |                                                         va_list args, char *buffer, uint16_t *buffer_at, | ||||||
|  |                                                         uint16_t buffer_size) { | ||||||
| #if defined(USE_ESP32) || defined(USE_LIBRETINY) | #if defined(USE_ESP32) || defined(USE_LIBRETINY) | ||||||
|     this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size); |     this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size); | ||||||
| #else | #else | ||||||
| @@ -180,7 +182,7 @@ class Logger : public Component { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Helper to format and send a log message to both console and callbacks |   // Helper to format and send a log message to both console and callbacks | ||||||
|   inline void HOT log_message_to_buffer_and_send_(int level, const char *tag, int line, const char *format, |   inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format, | ||||||
|                                                   va_list args) { |                                                   va_list args) { | ||||||
|     // Format to tx_buffer and prepare for output |     // Format to tx_buffer and prepare for output | ||||||
|     this->tx_buffer_at_ = 0;  // Initialize buffer position |     this->tx_buffer_at_ = 0;  // Initialize buffer position | ||||||
| @@ -194,11 +196,12 @@ class Logger : public Component { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Write the body of the log message to the buffer |   // Write the body of the log message to the buffer | ||||||
|   inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, int *buffer_at, int buffer_size) { |   inline void write_body_to_buffer_(const char *value, size_t length, char *buffer, uint16_t *buffer_at, | ||||||
|  |                                     uint16_t buffer_size) { | ||||||
|     // Calculate available space |     // Calculate available space | ||||||
|     const int available = buffer_size - *buffer_at; |     if (*buffer_at >= buffer_size) | ||||||
|     if (available <= 0) |  | ||||||
|       return; |       return; | ||||||
|  |     const uint16_t available = buffer_size - *buffer_at; | ||||||
|  |  | ||||||
|     // Determine copy length (minimum of remaining capacity and string length) |     // Determine copy length (minimum of remaining capacity and string length) | ||||||
|     const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available; |     const size_t copy_len = (length < static_cast<size_t>(available)) ? length : available; | ||||||
| @@ -211,7 +214,7 @@ class Logger : public Component { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Format string to explicit buffer with varargs |   // Format string to explicit buffer with varargs | ||||||
|   inline void printf_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, ...) { |   inline void printf_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, ...) { | ||||||
|     va_list arg; |     va_list arg; | ||||||
|     va_start(arg, format); |     va_start(arg, format); | ||||||
|     this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg); |     this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg); | ||||||
| @@ -222,41 +225,50 @@ class Logger : public Component { | |||||||
|   const char *get_uart_selection_(); |   const char *get_uart_selection_(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   // Group 4-byte aligned members first | ||||||
|   uint32_t baud_rate_; |   uint32_t baud_rate_; | ||||||
|   char *tx_buffer_{nullptr}; |   char *tx_buffer_{nullptr}; | ||||||
|   int tx_buffer_at_{0}; | #ifdef USE_ARDUINO | ||||||
|   int tx_buffer_size_{0}; |   Stream *hw_serial_{nullptr}; | ||||||
|  | #endif | ||||||
|  | #if defined(USE_ESP32) || defined(USE_LIBRETINY) | ||||||
|  |   void *main_task_ = nullptr;  // Only used for thread name identification | ||||||
|  | #endif | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |   // Task-specific recursion guards: | ||||||
|  |   // - Main task uses a dedicated member variable for efficiency | ||||||
|  |   // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create | ||||||
|  |   pthread_key_t log_recursion_key_;  // 4 bytes | ||||||
|  | #endif | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  |   uart_port_t uart_num_;  // 4 bytes (enum defaults to int size) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   // Large objects (internally aligned) | ||||||
|  |   std::map<std::string, uint8_t> log_levels_{}; | ||||||
|  |   CallbackManager<void(uint8_t, const char *, const char *)> log_callback_{}; | ||||||
|  |   CallbackManager<void(uint8_t)> level_callback_{}; | ||||||
|  | #ifdef USE_ESPHOME_TASK_LOG_BUFFER | ||||||
|  |   std::unique_ptr<logger::TaskLogBuffer> log_buffer_;  // Will be initialized with init_log_buffer | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   // Group smaller types together at the end | ||||||
|  |   uint16_t tx_buffer_at_{0}; | ||||||
|  |   uint16_t tx_buffer_size_{0}; | ||||||
|  |   uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE}; | ||||||
| #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) | ||||||
|   UARTSelection uart_{UART_SELECTION_UART0}; |   UARTSelection uart_{UART_SELECTION_UART0}; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LIBRETINY | #ifdef USE_LIBRETINY | ||||||
|   UARTSelection uart_{UART_SELECTION_DEFAULT}; |   UARTSelection uart_{UART_SELECTION_DEFAULT}; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|   Stream *hw_serial_{nullptr}; |  | ||||||
| #endif |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
|   uart_port_t uart_num_; |  | ||||||
| #endif |  | ||||||
|   std::map<std::string, int> log_levels_{}; |  | ||||||
|   CallbackManager<void(int, const char *, const char *)> log_callback_{}; |  | ||||||
|   int current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE}; |  | ||||||
| #ifdef USE_ESPHOME_TASK_LOG_BUFFER |  | ||||||
|   std::unique_ptr<logger::TaskLogBuffer> log_buffer_;  // Will be initialized with init_log_buffer |  | ||||||
| #endif |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   // Task-specific recursion guards: |  | ||||||
|   // - Main task uses a dedicated member variable for efficiency |  | ||||||
|   // - Other tasks use pthread TLS with a dynamically created key via pthread_key_create |  | ||||||
|   bool main_task_recursion_guard_{false}; |   bool main_task_recursion_guard_{false}; | ||||||
|   pthread_key_t log_recursion_key_; |  | ||||||
| #else | #else | ||||||
|   bool global_recursion_guard_{false};  // Simple global recursion guard for single-task platforms |   bool global_recursion_guard_{false};  // Simple global recursion guard for single-task platforms | ||||||
| #endif | #endif | ||||||
|   CallbackManager<void(int)> level_callback_{}; |  | ||||||
|  |  | ||||||
| #if defined(USE_ESP32) || defined(USE_LIBRETINY) | #if defined(USE_ESP32) || defined(USE_LIBRETINY) | ||||||
|   void *main_task_ = nullptr;  // Only used for thread name identification |  | ||||||
|   const char *HOT get_thread_name_() { |   const char *HOT get_thread_name_() { | ||||||
|     TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); |     TaskHandle_t current_task = xTaskGetCurrentTaskHandle(); | ||||||
|     if (current_task == main_task_) { |     if (current_task == main_task_) { | ||||||
| @@ -297,11 +309,10 @@ class Logger : public Component { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   inline void HOT write_header_to_buffer_(int level, const char *tag, int line, const char *thread_name, char *buffer, |   inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name, | ||||||
|                                           int *buffer_at, int buffer_size) { |                                           char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { | ||||||
|     // Format header |     // Format header | ||||||
|     if (level < 0) |     // uint8_t level is already bounded 0-255, just ensure it's <= 7 | ||||||
|       level = 0; |  | ||||||
|     if (level > 7) |     if (level > 7) | ||||||
|       level = 7; |       level = 7; | ||||||
|  |  | ||||||
| @@ -320,12 +331,12 @@ class Logger : public Component { | |||||||
|     this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line); |     this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   inline void HOT format_body_to_buffer_(char *buffer, int *buffer_at, int buffer_size, const char *format, |   inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, | ||||||
|                                          va_list args) { |                                          va_list args) { | ||||||
|     // Get remaining capacity in the buffer |     // Get remaining capacity in the buffer | ||||||
|     const int remaining = buffer_size - *buffer_at; |     if (*buffer_at >= buffer_size) | ||||||
|     if (remaining <= 0) |  | ||||||
|       return; |       return; | ||||||
|  |     const uint16_t remaining = buffer_size - *buffer_at; | ||||||
|  |  | ||||||
|     const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args); |     const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args); | ||||||
|  |  | ||||||
| @@ -334,7 +345,7 @@ class Logger : public Component { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Update buffer_at with the formatted length (handle truncation) |     // Update buffer_at with the formatted length (handle truncation) | ||||||
|     int formatted_len = (ret >= remaining) ? remaining : ret; |     uint16_t formatted_len = (ret >= remaining) ? remaining : ret; | ||||||
|     *buffer_at += formatted_len; |     *buffer_at += formatted_len; | ||||||
|  |  | ||||||
|     // Remove all trailing newlines right after formatting |     // Remove all trailing newlines right after formatting | ||||||
| @@ -343,18 +354,38 @@ class Logger : public Component { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   inline void HOT write_footer_to_buffer_(char *buffer, int *buffer_at, int buffer_size) { |   inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { | ||||||
|     static const int RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR); |     static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR); | ||||||
|     this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); |     this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |   // Disable loop when task buffer is empty (with USB CDC check) | ||||||
|  |   inline void disable_loop_when_buffer_empty_() { | ||||||
|  |     // Thread safety note: This is safe even if another task calls enable_loop_soon_any_context() | ||||||
|  |     // concurrently. If that happens between our check and disable_loop(), the enable request | ||||||
|  |     // will be processed on the next main loop iteration since: | ||||||
|  |     // - disable_loop() takes effect immediately | ||||||
|  |     // - enable_loop_soon_any_context() sets a pending flag that's checked at loop start | ||||||
|  | #if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO) | ||||||
|  |     // Only disable if not using USB CDC (which needs loop for connection detection) | ||||||
|  |     if (this->uart_ != UART_SELECTION_USB_CDC) { | ||||||
|  |       this->disable_loop(); | ||||||
|  |     } | ||||||
|  | #else | ||||||
|  |     // No USB CDC support, always safe to disable | ||||||
|  |     this->disable_loop(); | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| extern Logger *global_logger;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | extern Logger *global_logger;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|  |  | ||||||
| class LoggerMessageTrigger : public Trigger<int, const char *, const char *> { | class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> { | ||||||
|  public: |  public: | ||||||
|   explicit LoggerMessageTrigger(Logger *parent, int level) { |   explicit LoggerMessageTrigger(Logger *parent, uint8_t level) { | ||||||
|     this->level_ = level; |     this->level_ = level; | ||||||
|     parent->add_on_log_callback([this](int level, const char *tag, const char *message) { |     parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) { | ||||||
|       if (level <= this->level_) { |       if (level <= this->level_) { | ||||||
|         this->trigger(level, tag, message); |         this->trigger(level, tag, message); | ||||||
|       } |       } | ||||||
| @@ -362,7 +393,7 @@ class LoggerMessageTrigger : public Trigger<int, const char *, const char *> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   int level_; |   uint8_t level_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace logger | }  // namespace logger | ||||||
|   | |||||||
| @@ -466,7 +466,7 @@ LVGL_SCHEMA = cv.All( | |||||||
|                 ): lvalid.lv_color, |                 ): lvalid.lv_color, | ||||||
|                 cv.Optional(df.CONF_THEME): cv.Schema( |                 cv.Optional(df.CONF_THEME): cv.Schema( | ||||||
|                     { |                     { | ||||||
|                         cv.Optional(name): obj_schema(w) |                         cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA) | ||||||
|                         for name, w in WIDGET_TYPES.items() |                         for name, w in WIDGET_TYPES.items() | ||||||
|                     } |                     } | ||||||
|                 ), |                 ), | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ from esphome.core.config import StartupTrigger | |||||||
| from esphome.schema_extractors import SCHEMA_EXTRACT | from esphome.schema_extractors import SCHEMA_EXTRACT | ||||||
|  |  | ||||||
| from . import defines as df, lv_validation as lvalid | from . import defines as df, lv_validation as lvalid | ||||||
| from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR | from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR, TYPE_GRID | ||||||
| from .helpers import add_lv_use, requires_component, validate_printf | from .helpers import add_lv_use, requires_component, validate_printf | ||||||
| from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity | from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity | ||||||
| from .lvcode import LvglComponent, lv_event_t_ptr | from .lvcode import LvglComponent, lv_event_t_ptr | ||||||
| @@ -349,7 +349,60 @@ def obj_schema(widget_type: WidgetType): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_grid_layout(config): | ||||||
|  |     layout = config[df.CONF_LAYOUT] | ||||||
|  |     rows = len(layout[df.CONF_GRID_ROWS]) | ||||||
|  |     columns = len(layout[df.CONF_GRID_COLUMNS]) | ||||||
|  |     used_cells = [[None] * columns for _ in range(rows)] | ||||||
|  |     for index, widget in enumerate(config[df.CONF_WIDGETS]): | ||||||
|  |         _, w = next(iter(widget.items())) | ||||||
|  |         if (df.CONF_GRID_CELL_COLUMN_POS in w) != (df.CONF_GRID_CELL_ROW_POS in w): | ||||||
|  |             # pylint: disable=raise-missing-from | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 "Both row and column positions must be specified, or both omitted", | ||||||
|  |                 [df.CONF_WIDGETS, index], | ||||||
|  |             ) | ||||||
|  |         if df.CONF_GRID_CELL_ROW_POS in w: | ||||||
|  |             row = w[df.CONF_GRID_CELL_ROW_POS] | ||||||
|  |             column = w[df.CONF_GRID_CELL_COLUMN_POS] | ||||||
|  |         else: | ||||||
|  |             try: | ||||||
|  |                 row, column = next( | ||||||
|  |                     (r_idx, c_idx) | ||||||
|  |                     for r_idx, row in enumerate(used_cells) | ||||||
|  |                     for c_idx, value in enumerate(row) | ||||||
|  |                     if value is None | ||||||
|  |                 ) | ||||||
|  |             except StopIteration: | ||||||
|  |                 # pylint: disable=raise-missing-from | ||||||
|  |                 raise cv.Invalid( | ||||||
|  |                     "No free cells available in grid layout", [df.CONF_WIDGETS, index] | ||||||
|  |                 ) | ||||||
|  |             w[df.CONF_GRID_CELL_ROW_POS] = row | ||||||
|  |             w[df.CONF_GRID_CELL_COLUMN_POS] = column | ||||||
|  |  | ||||||
|  |         for i in range(w[df.CONF_GRID_CELL_ROW_SPAN]): | ||||||
|  |             for j in range(w[df.CONF_GRID_CELL_COLUMN_SPAN]): | ||||||
|  |                 if row + i >= rows or column + j >= columns: | ||||||
|  |                     # pylint: disable=raise-missing-from | ||||||
|  |                     raise cv.Invalid( | ||||||
|  |                         f"Cell at {row}/{column} span {w[df.CONF_GRID_CELL_ROW_SPAN]}x{w[df.CONF_GRID_CELL_COLUMN_SPAN]} " | ||||||
|  |                         f"exceeds grid size {rows}x{columns}", | ||||||
|  |                         [df.CONF_WIDGETS, index], | ||||||
|  |                     ) | ||||||
|  |                 if used_cells[row + i][column + j] is not None: | ||||||
|  |                     # pylint: disable=raise-missing-from | ||||||
|  |                     raise cv.Invalid( | ||||||
|  |                         f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}", | ||||||
|  |                         [df.CONF_WIDGETS, index], | ||||||
|  |                     ) | ||||||
|  |                 used_cells[row + i][column + j] = index | ||||||
|  |  | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| LAYOUT_SCHEMAS = {} | LAYOUT_SCHEMAS = {} | ||||||
|  | LAYOUT_VALIDATORS = {TYPE_GRID: _validate_grid_layout} | ||||||
|  |  | ||||||
| ALIGN_TO_SCHEMA = { | ALIGN_TO_SCHEMA = { | ||||||
|     cv.Optional(df.CONF_ALIGN_TO): cv.Schema( |     cv.Optional(df.CONF_ALIGN_TO): cv.Schema( | ||||||
| @@ -402,8 +455,8 @@ LAYOUT_SCHEMA = { | |||||||
| } | } | ||||||
|  |  | ||||||
| GRID_CELL_SCHEMA = { | GRID_CELL_SCHEMA = { | ||||||
|     cv.Required(df.CONF_GRID_CELL_ROW_POS): cv.positive_int, |     cv.Optional(df.CONF_GRID_CELL_ROW_POS): cv.positive_int, | ||||||
|     cv.Required(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int, |     cv.Optional(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int, | ||||||
|     cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int, |     cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int, | ||||||
|     cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int, |     cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int, | ||||||
|     cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, |     cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments, | ||||||
| @@ -454,9 +507,13 @@ def container_validator(schema, widget_type: WidgetType): | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def validator(value): |     def validator(value): | ||||||
|         result = schema |  | ||||||
|         if w_sch := widget_type.schema: |         if w_sch := widget_type.schema: | ||||||
|             result = result.extend(w_sch) |             if isinstance(w_sch, dict): | ||||||
|  |                 w_sch = cv.Schema(w_sch) | ||||||
|  |             # order is important here to preserve extras | ||||||
|  |             result = w_sch.extend(schema) | ||||||
|  |         else: | ||||||
|  |             result = schema | ||||||
|         ltype = df.TYPE_NONE |         ltype = df.TYPE_NONE | ||||||
|         if value and (layout := value.get(df.CONF_LAYOUT)): |         if value and (layout := value.get(df.CONF_LAYOUT)): | ||||||
|             if not isinstance(layout, dict): |             if not isinstance(layout, dict): | ||||||
| @@ -470,7 +527,10 @@ def container_validator(schema, widget_type: WidgetType): | |||||||
|         result = result.extend( |         result = result.extend( | ||||||
|             LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE]) |             LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE]) | ||||||
|         ) |         ) | ||||||
|         return result(value) |         value = result(value) | ||||||
|  |         if layout_validator := LAYOUT_VALIDATORS.get(ltype): | ||||||
|  |             value = layout_validator(value) | ||||||
|  |         return value | ||||||
|  |  | ||||||
|     return validator |     return validator | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
| from esphome.core import ID | from esphome.core import ID | ||||||
| from esphome.cpp_generator import MockObj |  | ||||||
|  |  | ||||||
| from .defines import ( | from .defines import ( | ||||||
|     CONF_STYLE_DEFINITIONS, |     CONF_STYLE_DEFINITIONS, | ||||||
| @@ -13,12 +12,13 @@ from .defines import ( | |||||||
|     literal, |     literal, | ||||||
| ) | ) | ||||||
| from .helpers import add_lv_use | from .helpers import add_lv_use | ||||||
| from .lvcode import LambdaContext, LocalVariable, lv, lv_assign, lv_variable | from .lvcode import LambdaContext, LocalVariable, lv | ||||||
| from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP | from .schemas import ALL_STYLES, FULL_STYLE_SCHEMA, STYLE_REMAP | ||||||
| from .types import ObjUpdateAction, lv_lambda_t, lv_obj_t, lv_obj_t_ptr, lv_style_t | from .types import ObjUpdateAction, lv_obj_t, lv_style_t | ||||||
| from .widgets import ( | from .widgets import ( | ||||||
|     Widget, |     Widget, | ||||||
|     add_widgets, |     add_widgets, | ||||||
|  |     collect_parts, | ||||||
|     set_obj_properties, |     set_obj_properties, | ||||||
|     theme_widget_map, |     theme_widget_map, | ||||||
|     wait_for_widgets, |     wait_for_widgets, | ||||||
| @@ -37,12 +37,18 @@ async def style_set(svar, style): | |||||||
|             lv.call(f"style_set_{remapped_prop}", svar, literal(value)) |             lv.call(f"style_set_{remapped_prop}", svar, literal(value)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def create_style(style, id_name): | ||||||
|  |     style_id = ID(id_name, True, lv_style_t) | ||||||
|  |     svar = cg.new_Pvariable(style_id) | ||||||
|  |     lv.style_init(svar) | ||||||
|  |     await style_set(svar, style) | ||||||
|  |     return svar | ||||||
|  |  | ||||||
|  |  | ||||||
| async def styles_to_code(config): | async def styles_to_code(config): | ||||||
|     """Convert styles to C__ code.""" |     """Convert styles to C__ code.""" | ||||||
|     for style in config.get(CONF_STYLE_DEFINITIONS, ()): |     for style in config.get(CONF_STYLE_DEFINITIONS, ()): | ||||||
|         svar = cg.new_Pvariable(style[CONF_ID]) |         await create_style(style, style[CONF_ID].id) | ||||||
|         lv.style_init(svar) |  | ||||||
|         await style_set(svar, style) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @automation.register_action( | @automation.register_action( | ||||||
| @@ -68,16 +74,18 @@ async def theme_to_code(config): | |||||||
|     if theme := config.get(CONF_THEME): |     if theme := config.get(CONF_THEME): | ||||||
|         add_lv_use(CONF_THEME) |         add_lv_use(CONF_THEME) | ||||||
|         for w_name, style in theme.items(): |         for w_name, style in theme.items(): | ||||||
|             if not isinstance(style, dict): |             # Work around Python 3.10 bug with nested async comprehensions | ||||||
|                 continue |             # With Python 3.11 this could be simplified | ||||||
|  |             styles = {} | ||||||
|             lname = "lv_theme_apply_" + w_name |             for part, states in collect_parts(style).items(): | ||||||
|             apply = lv_variable(lv_lambda_t, lname) |                 styles[part] = { | ||||||
|             theme_widget_map[w_name] = apply |                     state: await create_style( | ||||||
|             ow = Widget.create("obj", MockObj(ID("obj")), obj_spec) |                         props, | ||||||
|             async with LambdaContext([(lv_obj_t_ptr, "obj")], where=w_name) as context: |                         "_lv_theme_style_" + w_name + "_" + part + "_" + state, | ||||||
|                 await set_obj_properties(ow, style) |                     ) | ||||||
|             lv_assign(apply, await context.get_lambda()) |                     for state, props in states.items() | ||||||
|  |                 } | ||||||
|  |             theme_widget_map[w_name] = styles | ||||||
|  |  | ||||||
|  |  | ||||||
| async def add_top_layer(lv_component, config): | async def add_top_layer(lv_component, config): | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ from esphome.config_validation import Invalid | |||||||
| from esphome.const import CONF_DEFAULT, CONF_GROUP, CONF_ID, CONF_STATE, CONF_TYPE | from esphome.const import CONF_DEFAULT, CONF_GROUP, CONF_ID, CONF_STATE, CONF_TYPE | ||||||
| from esphome.core import ID, TimePeriod | from esphome.core import ID, TimePeriod | ||||||
| from esphome.coroutine import FakeAwaitable | from esphome.coroutine import FakeAwaitable | ||||||
| from esphome.cpp_generator import CallExpression, MockObj | from esphome.cpp_generator import MockObj | ||||||
|  |  | ||||||
| from ..defines import ( | from ..defines import ( | ||||||
|     CONF_FLEX_ALIGN_CROSS, |     CONF_FLEX_ALIGN_CROSS, | ||||||
| @@ -453,7 +453,17 @@ async def widget_to_code(w_cnfig, w_type: WidgetType, parent): | |||||||
|  |  | ||||||
|     w = Widget.create(wid, var, spec, w_cnfig) |     w = Widget.create(wid, var, spec, w_cnfig) | ||||||
|     if theme := theme_widget_map.get(w_type): |     if theme := theme_widget_map.get(w_type): | ||||||
|         lv_add(CallExpression(theme, w.obj)) |         for part, states in theme.items(): | ||||||
|  |             part = "LV_PART_" + part.upper() | ||||||
|  |             for state, style in states.items(): | ||||||
|  |                 state = "LV_STATE_" + state.upper() | ||||||
|  |                 if state == "LV_STATE_DEFAULT": | ||||||
|  |                     lv_state = literal(part) | ||||||
|  |                 elif part == "LV_PART_MAIN": | ||||||
|  |                     lv_state = literal(state) | ||||||
|  |                 else: | ||||||
|  |                     lv_state = join_enums((state, part)) | ||||||
|  |                 lv.obj_add_style(w.obj, style, lv_state) | ||||||
|     await set_obj_properties(w, w_cnfig) |     await set_obj_properties(w, w_cnfig) | ||||||
|     await add_widgets(w, w_cnfig) |     await add_widgets(w, w_cnfig) | ||||||
|     await spec.to_code(w, w_cnfig) |     await spec.to_code(w, w_cnfig) | ||||||
|   | |||||||
| @@ -1,8 +1,15 @@ | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MODE, CONF_VALUE | from esphome.const import CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_MODE, CONF_VALUE | ||||||
|  |  | ||||||
| from ..defines import BAR_MODES, CONF_ANIMATED, CONF_INDICATOR, CONF_MAIN, literal | from ..defines import ( | ||||||
| from ..lv_validation import animated, get_start_value, lv_float |     BAR_MODES, | ||||||
|  |     CONF_ANIMATED, | ||||||
|  |     CONF_INDICATOR, | ||||||
|  |     CONF_MAIN, | ||||||
|  |     CONF_START_VALUE, | ||||||
|  |     literal, | ||||||
|  | ) | ||||||
|  | from ..lv_validation import animated, lv_int | ||||||
| from ..lvcode import lv | from ..lvcode import lv | ||||||
| from ..types import LvNumber, NumberType | from ..types import LvNumber, NumberType | ||||||
| from . import Widget | from . import Widget | ||||||
| @@ -10,22 +17,30 @@ from . import Widget | |||||||
| # Note this file cannot be called "bar.py" because that name is disallowed. | # Note this file cannot be called "bar.py" because that name is disallowed. | ||||||
|  |  | ||||||
| CONF_BAR = "bar" | CONF_BAR = "bar" | ||||||
| BAR_MODIFY_SCHEMA = cv.Schema( |  | ||||||
|     { |  | ||||||
|         cv.Optional(CONF_VALUE): lv_float, | def validate_bar(config): | ||||||
|         cv.Optional(CONF_ANIMATED, default=True): animated, |     if config.get(CONF_MODE) != "LV_BAR_MODE_RANGE" and CONF_START_VALUE in config: | ||||||
|     } |         raise cv.Invalid( | ||||||
| ) |             f"{CONF_START_VALUE} is only allowed when {CONF_MODE} is set to 'RANGE'" | ||||||
|  |         ) | ||||||
|  |     if (CONF_MIN_VALUE in config) != (CONF_MAX_VALUE in config): | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"If either {CONF_MIN_VALUE} or {CONF_MAX_VALUE} is set, both must be set" | ||||||
|  |         ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| BAR_SCHEMA = cv.Schema( | BAR_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Optional(CONF_VALUE): lv_float, |         cv.Optional(CONF_VALUE): lv_int, | ||||||
|         cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, |         cv.Optional(CONF_START_VALUE): lv_int, | ||||||
|         cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, |         cv.Optional(CONF_MIN_VALUE): lv_int, | ||||||
|         cv.Optional(CONF_MODE, default="NORMAL"): BAR_MODES.one_of, |         cv.Optional(CONF_MAX_VALUE): lv_int, | ||||||
|  |         cv.Optional(CONF_MODE): BAR_MODES.one_of, | ||||||
|         cv.Optional(CONF_ANIMATED, default=True): animated, |         cv.Optional(CONF_ANIMATED, default=True): animated, | ||||||
|     } |     } | ||||||
| ) | ).add_extra(validate_bar) | ||||||
|  |  | ||||||
|  |  | ||||||
| class BarType(NumberType): | class BarType(NumberType): | ||||||
| @@ -35,17 +50,23 @@ class BarType(NumberType): | |||||||
|             LvNumber("lv_bar_t"), |             LvNumber("lv_bar_t"), | ||||||
|             parts=(CONF_MAIN, CONF_INDICATOR), |             parts=(CONF_MAIN, CONF_INDICATOR), | ||||||
|             schema=BAR_SCHEMA, |             schema=BAR_SCHEMA, | ||||||
|             modify_schema=BAR_MODIFY_SCHEMA, |  | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     async def to_code(self, w: Widget, config): |     async def to_code(self, w: Widget, config): | ||||||
|         var = w.obj |         var = w.obj | ||||||
|  |         if mode := config.get(CONF_MODE): | ||||||
|  |             lv.bar_set_mode(var, literal(mode)) | ||||||
|  |         is_animated = literal(config[CONF_ANIMATED]) | ||||||
|         if CONF_MIN_VALUE in config: |         if CONF_MIN_VALUE in config: | ||||||
|             lv.bar_set_range(var, config[CONF_MIN_VALUE], config[CONF_MAX_VALUE]) |             lv.bar_set_range( | ||||||
|             lv.bar_set_mode(var, literal(config[CONF_MODE])) |                 var, | ||||||
|         value = await get_start_value(config) |                 await lv_int.process(config[CONF_MIN_VALUE]), | ||||||
|         if value is not None: |                 await lv_int.process(config[CONF_MAX_VALUE]), | ||||||
|             lv.bar_set_value(var, value, literal(config[CONF_ANIMATED])) |             ) | ||||||
|  |         if value := await lv_int.process(config.get(CONF_VALUE)): | ||||||
|  |             lv.bar_set_value(var, value, is_animated) | ||||||
|  |         if start_value := await lv_int.process(config.get(CONF_START_VALUE)): | ||||||
|  |             lv.bar_set_start_value(var, start_value, is_animated) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def animated(self): |     def animated(self): | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import esphome.config_validation as cv | |||||||
| from esphome.const import CONF_SIZE, CONF_TEXT | from esphome.const import CONF_SIZE, CONF_TEXT | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
|  |  | ||||||
| from ..defines import CONF_MAIN, literal | from ..defines import CONF_MAIN | ||||||
| from ..lv_validation import color, color_retmapper, lv_text | from ..lv_validation import color, color_retmapper, lv_text | ||||||
| from ..lvcode import LocalVariable, lv, lv_expr | from ..lvcode import LocalVariable, lv, lv_expr | ||||||
| from ..schemas import TEXT_SCHEMA | from ..schemas import TEXT_SCHEMA | ||||||
| @@ -34,7 +34,7 @@ class QrCodeType(WidgetType): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def get_uses(self): |     def get_uses(self): | ||||||
|         return ("canvas", "img") |         return ("canvas", "img", "label") | ||||||
|  |  | ||||||
|     def obj_creator(self, parent: MockObjClass, config: dict): |     def obj_creator(self, parent: MockObjClass, config: dict): | ||||||
|         dark_color = color_retmapper(config[CONF_DARK_COLOR]) |         dark_color = color_retmapper(config[CONF_DARK_COLOR]) | ||||||
| @@ -45,10 +45,8 @@ class QrCodeType(WidgetType): | |||||||
|     async def to_code(self, w: Widget, config): |     async def to_code(self, w: Widget, config): | ||||||
|         if (value := config.get(CONF_TEXT)) is not None: |         if (value := config.get(CONF_TEXT)) is not None: | ||||||
|             value = await lv_text.process(value) |             value = await lv_text.process(value) | ||||||
|             with LocalVariable( |             with LocalVariable("qr_text", cg.std_string, value, modifier="") as str_obj: | ||||||
|                 "qr_text", cg.const_char_ptr, value, modifier="" |                 lv.qrcode_update(w.obj, str_obj.c_str(), str_obj.size()) | ||||||
|             ) as str_obj: |  | ||||||
|                 lv.qrcode_update(w.obj, str_obj, literal(f"strlen({str_obj})")) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| qr_code_spec = QrCodeType() | qr_code_spec = QrCodeType() | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ namespace m5stack_8angle { | |||||||
| static const char *const TAG = "m5stack_8angle.light"; | static const char *const TAG = "m5stack_8angle.light"; | ||||||
|  |  | ||||||
| void M5Stack8AngleLightOutput::setup() { | void M5Stack8AngleLightOutput::setup() { | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator; | ||||||
|   this->buf_ = allocator.allocate(M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); |   this->buf_ = allocator.allocate(M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); | ||||||
|   if (this->buf_ == nullptr) { |   if (this->buf_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Failed to allocate buffer of size %u", M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); |     ESP_LOGE(TAG, "Failed to allocate buffer of size %u", M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); | ||||||
|   | |||||||
| @@ -88,12 +88,7 @@ async def to_code(config): | |||||||
|     if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version( |     if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version( | ||||||
|         5, 0, 0 |         5, 0, 0 | ||||||
|     ): |     ): | ||||||
|         add_idf_component( |         add_idf_component(name="espressif/mdns", ref="1.8.2") | ||||||
|             name="mdns", |  | ||||||
|             repo="https://github.com/espressif/esp-protocols.git", |  | ||||||
|             ref="mdns-v1.8.2", |  | ||||||
|             path="components/mdns", |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     cg.add_define("USE_MDNS") |     cg.add_define("USE_MDNS") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,9 +11,9 @@ from esphome.const import ( | |||||||
|     CONF_VOLUME, |     CONF_VOLUME, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.coroutine import coroutine_with_priority | from esphome.coroutine import coroutine_with_priority | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
|  |  | ||||||
| @@ -81,7 +81,7 @@ IsAnnouncingCondition = media_player_ns.class_( | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_media_player_core_(var, config): | async def setup_media_player_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "media_player") | ||||||
|     for conf in config.get(CONF_ON_STATE, []): |     for conf in config.get(CONF_ON_STATE, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|         await automation.build_automation(trigger, [], conf) |         await automation.build_automation(trigger, [], conf) | ||||||
| @@ -143,6 +143,8 @@ _MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | |||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | _MEDIA_PLAYER_SCHEMA.add_extra(entity_duplicate_validator("media_player")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def media_player_schema( | def media_player_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
| @@ -166,7 +168,6 @@ def media_player_schema( | |||||||
| MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer) | MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer) | ||||||
| MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player")) | MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player")) | ||||||
|  |  | ||||||
|  |  | ||||||
| MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id( | MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -449,11 +449,7 @@ async def to_code(config): | |||||||
|     cg.add_define("USE_MICRO_WAKE_WORD") |     cg.add_define("USE_MICRO_WAKE_WORD") | ||||||
|     cg.add_define("USE_OTA_STATE_CALLBACK") |     cg.add_define("USE_OTA_STATE_CALLBACK") | ||||||
|  |  | ||||||
|     esp32.add_idf_component( |     esp32.add_idf_component(name="espressif/esp-tflite-micro", ref="1.3.3~1") | ||||||
|         name="esp-tflite-micro", |  | ||||||
|         repo="https://github.com/espressif/esp-tflite-micro", |  | ||||||
|         ref="v1.3.3.1", |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     cg.add_build_flag("-DTF_LITE_STATIC_MEMORY") |     cg.add_build_flag("-DTF_LITE_STATIC_MEMORY") | ||||||
|     cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON") |     cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON") | ||||||
|   | |||||||
| @@ -472,3 +472,4 @@ async def to_code(config): | |||||||
|         cg.add(var.set_writer(lambda_)) |         cg.add(var.set_writer(lambda_)) | ||||||
|     await display.register_display(var, config) |     await display.register_display(var, config) | ||||||
|     await spi.register_spi_device(var, config) |     await spi.register_spi_device(var, config) | ||||||
|  |     cg.add(var.set_write_only(True)) | ||||||
|   | |||||||
| @@ -64,6 +64,14 @@ class ModbusDevice { | |||||||
|     this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); |     this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); | ||||||
|   } |   } | ||||||
|   void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); } |   void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); } | ||||||
|  |   void send_error(uint8_t function_code, uint8_t exception_code) { | ||||||
|  |     std::vector<uint8_t> error_response; | ||||||
|  |     error_response.reserve(3); | ||||||
|  |     error_response.push_back(this->address_); | ||||||
|  |     error_response.push_back(function_code | 0x80); | ||||||
|  |     error_response.push_back(exception_code); | ||||||
|  |     this->send_raw(error_response); | ||||||
|  |   } | ||||||
|   // If more than one device is connected block sending a new command before a response is received |   // If more than one device is connected block sending a new command before a response is received | ||||||
|   bool waiting_for_response() { return parent_->waiting_for_response != 0; } |   bool waiting_for_response() { return parent_->waiting_for_response != 0; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -112,6 +112,22 @@ TYPE_REGISTER_MAP = { | |||||||
|     "FP32_R": 2, |     "FP32_R": 2, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | CPP_TYPE_REGISTER_MAP = { | ||||||
|  |     "RAW": cg.uint16, | ||||||
|  |     "U_WORD": cg.uint16, | ||||||
|  |     "S_WORD": cg.int16, | ||||||
|  |     "U_DWORD": cg.uint32, | ||||||
|  |     "U_DWORD_R": cg.uint32, | ||||||
|  |     "S_DWORD": cg.int32, | ||||||
|  |     "S_DWORD_R": cg.int32, | ||||||
|  |     "U_QWORD": cg.uint64, | ||||||
|  |     "U_QWORD_R": cg.uint64, | ||||||
|  |     "S_QWORD": cg.int64, | ||||||
|  |     "S_QWORD_R": cg.int64, | ||||||
|  |     "FP32": cg.float_, | ||||||
|  |     "FP32_R": cg.float_, | ||||||
|  | } | ||||||
|  |  | ||||||
| ModbusCommandSentTrigger = modbus_controller_ns.class_( | ModbusCommandSentTrigger = modbus_controller_ns.class_( | ||||||
|     "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) |     "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) | ||||||
| ) | ) | ||||||
| @@ -285,21 +301,24 @@ async def to_code(config): | |||||||
|     cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) |     cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) | ||||||
|     if CONF_SERVER_REGISTERS in config: |     if CONF_SERVER_REGISTERS in config: | ||||||
|         for server_register in config[CONF_SERVER_REGISTERS]: |         for server_register in config[CONF_SERVER_REGISTERS]: | ||||||
|  |             server_register_var = cg.new_Pvariable( | ||||||
|  |                 server_register[CONF_ID], | ||||||
|  |                 server_register[CONF_ADDRESS], | ||||||
|  |                 server_register[CONF_VALUE_TYPE], | ||||||
|  |                 TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]], | ||||||
|  |             ) | ||||||
|  |             cpp_type = CPP_TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]] | ||||||
|             cg.add( |             cg.add( | ||||||
|                 var.add_server_register( |                 server_register_var.set_read_lambda( | ||||||
|                     cg.new_Pvariable( |                     cg.TemplateArguments(cpp_type), | ||||||
|                         server_register[CONF_ID], |                     await cg.process_lambda( | ||||||
|                         server_register[CONF_ADDRESS], |                         server_register[CONF_READ_LAMBDA], | ||||||
|                         server_register[CONF_VALUE_TYPE], |                         [(cg.uint16, "address")], | ||||||
|                         TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]], |                         return_type=cpp_type, | ||||||
|                         await cg.process_lambda( |                     ), | ||||||
|                             server_register[CONF_READ_LAMBDA], |  | ||||||
|                             [], |  | ||||||
|                             return_type=cg.float_, |  | ||||||
|                         ), |  | ||||||
|                     ) |  | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |             cg.add(var.add_server_register(server_register_var)) | ||||||
|     await register_modbus_device(var, config) |     await register_modbus_device(var, config) | ||||||
|     for conf in config.get(CONF_ON_COMMAND_SENT, []): |     for conf in config.get(CONF_ON_COMMAND_SENT, []): | ||||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|   | |||||||
| @@ -117,12 +117,17 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t | |||||||
|     bool found = false; |     bool found = false; | ||||||
|     for (auto *server_register : this->server_registers_) { |     for (auto *server_register : this->server_registers_) { | ||||||
|       if (server_register->address == current_address) { |       if (server_register->address == current_address) { | ||||||
|         float value = server_register->read_lambda(); |         if (!server_register->read_lambda) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         int64_t value = server_register->read_lambda(); | ||||||
|  |         ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.", | ||||||
|  |                  server_register->address, static_cast<size_t>(server_register->value_type), | ||||||
|  |                  server_register->register_count, server_register->format_value(value).c_str()); | ||||||
|  |  | ||||||
|         ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.", |         std::vector<uint16_t> payload; | ||||||
|                  server_register->address, static_cast<uint8_t>(server_register->value_type), |         payload.reserve(server_register->register_count * 2); | ||||||
|                  server_register->register_count, value); |         number_to_payload(payload, value, server_register->value_type); | ||||||
|         std::vector<uint16_t> payload = float_to_payload(value, server_register->value_type); |  | ||||||
|         sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend()); |         sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend()); | ||||||
|         current_address += server_register->register_count; |         current_address += server_register->register_count; | ||||||
|         found = true; |         found = true; | ||||||
| @@ -132,11 +137,7 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t | |||||||
|  |  | ||||||
|     if (!found) { |     if (!found) { | ||||||
|       ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address); |       ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address); | ||||||
|       std::vector<uint8_t> error_response; |       send_error(function_code, 0x02); | ||||||
|       error_response.push_back(this->address_); |  | ||||||
|       error_response.push_back(0x81); |  | ||||||
|       error_response.push_back(0x02); |  | ||||||
|       this->send_raw(error_response); |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -63,6 +63,10 @@ enum class SensorValueType : uint8_t { | |||||||
|   FP32_R = 0xD |   FP32_R = 0xD | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | inline bool value_type_is_float(SensorValueType v) { | ||||||
|  |   return v == SensorValueType::FP32 || v == SensorValueType::FP32_R; | ||||||
|  | } | ||||||
|  |  | ||||||
| inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { | inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { | ||||||
|   switch (reg_type) { |   switch (reg_type) { | ||||||
|     case ModbusRegisterType::COIL: |     case ModbusRegisterType::COIL: | ||||||
| @@ -253,18 +257,53 @@ class SensorItem { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| class ServerRegister { | class ServerRegister { | ||||||
|  |   using ReadLambda = std::function<int64_t()>; | ||||||
|  |  | ||||||
|  public: |  public: | ||||||
|   ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count, |   ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) { | ||||||
|                  std::function<float()> read_lambda) { |  | ||||||
|     this->address = address; |     this->address = address; | ||||||
|     this->value_type = value_type; |     this->value_type = value_type; | ||||||
|     this->register_count = register_count; |     this->register_count = register_count; | ||||||
|     this->read_lambda = std::move(read_lambda); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   template<typename T> void set_read_lambda(const std::function<T(uint16_t address)> &&user_read_lambda) { | ||||||
|  |     this->read_lambda = [this, user_read_lambda]() -> int64_t { | ||||||
|  |       T user_value = user_read_lambda(this->address); | ||||||
|  |       if constexpr (std::is_same_v<T, float>) { | ||||||
|  |         return bit_cast<uint32_t>(user_value); | ||||||
|  |       } else { | ||||||
|  |         return static_cast<int64_t>(user_value); | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Formats a raw value into a string representation based on the value type for debugging | ||||||
|  |   std::string format_value(int64_t value) const { | ||||||
|  |     switch (this->value_type) { | ||||||
|  |       case SensorValueType::U_WORD: | ||||||
|  |       case SensorValueType::U_DWORD: | ||||||
|  |       case SensorValueType::U_DWORD_R: | ||||||
|  |       case SensorValueType::U_QWORD: | ||||||
|  |       case SensorValueType::U_QWORD_R: | ||||||
|  |         return std::to_string(static_cast<uint64_t>(value)); | ||||||
|  |       case SensorValueType::S_WORD: | ||||||
|  |       case SensorValueType::S_DWORD: | ||||||
|  |       case SensorValueType::S_DWORD_R: | ||||||
|  |       case SensorValueType::S_QWORD: | ||||||
|  |       case SensorValueType::S_QWORD_R: | ||||||
|  |         return std::to_string(value); | ||||||
|  |       case SensorValueType::FP32_R: | ||||||
|  |       case SensorValueType::FP32: | ||||||
|  |         return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value))); | ||||||
|  |       default: | ||||||
|  |         return std::to_string(value); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   uint16_t address{0}; |   uint16_t address{0}; | ||||||
|   SensorValueType value_type{SensorValueType::RAW}; |   SensorValueType value_type{SensorValueType::RAW}; | ||||||
|   uint8_t register_count{0}; |   uint8_t register_count{0}; | ||||||
|   std::function<float()> read_lambda; |   ReadLambda read_lambda; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // ModbusController::create_register_ranges_ tries to optimize register range | // ModbusController::create_register_ranges_ tries to optimize register range | ||||||
| @@ -444,7 +483,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { | |||||||
|   void on_modbus_data(const std::vector<uint8_t> &data) override; |   void on_modbus_data(const std::vector<uint8_t> &data) override; | ||||||
|   /// called when a modbus error response was received |   /// called when a modbus error response was received | ||||||
|   void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; |   void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; | ||||||
|   /// called when a modbus request (function code 3 or 4) was parsed without errors |   /// called when a modbus request (function code 0x03 or 0x04) was parsed without errors | ||||||
|   void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final; |   void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final; | ||||||
|   /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue |   /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue | ||||||
|   void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data); |   void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data); | ||||||
| @@ -529,7 +568,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem | |||||||
|   int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask); |   int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask); | ||||||
|  |  | ||||||
|   float float_value; |   float float_value; | ||||||
|   if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) { |   if (value_type_is_float(item.sensor_value_type)) { | ||||||
|     float_value = bit_cast<float>(static_cast<uint32_t>(number)); |     float_value = bit_cast<float>(static_cast<uint32_t>(number)); | ||||||
|   } else { |   } else { | ||||||
|     float_value = static_cast<float>(number); |     float_value = static_cast<float>(number); | ||||||
| @@ -541,7 +580,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem | |||||||
| inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) { | inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) { | ||||||
|   int64_t val; |   int64_t val; | ||||||
|  |  | ||||||
|   if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) { |   if (value_type_is_float(value_type)) { | ||||||
|     val = bit_cast<uint32_t>(value); |     val = bit_cast<uint32_t>(value); | ||||||
|   } else { |   } else { | ||||||
|     val = llroundf(value); |     val = llroundf(value); | ||||||
|   | |||||||
| @@ -68,6 +68,7 @@ def AUTO_LOAD(): | |||||||
|  |  | ||||||
| CONF_DISCOVER_IP = "discover_ip" | CONF_DISCOVER_IP = "discover_ip" | ||||||
| CONF_IDF_SEND_ASYNC = "idf_send_async" | CONF_IDF_SEND_ASYNC = "idf_send_async" | ||||||
|  | CONF_WAIT_FOR_CONNECTION = "wait_for_connection" | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_message_just_topic(value): | def validate_message_just_topic(value): | ||||||
| @@ -298,6 +299,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean, |             cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_WAIT_FOR_CONNECTION, default=False): cv.boolean, | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
|     validate_config, |     validate_config, | ||||||
| @@ -453,6 +455,8 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) |     cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE])) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_wait_for_connection(config[CONF_WAIT_FOR_CONNECTION])) | ||||||
|  |  | ||||||
|  |  | ||||||
| MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( | MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -17,7 +17,8 @@ enum class MQTTClientDisconnectReason : int8_t { | |||||||
|   MQTT_MALFORMED_CREDENTIALS = 4, |   MQTT_MALFORMED_CREDENTIALS = 4, | ||||||
|   MQTT_NOT_AUTHORIZED = 5, |   MQTT_NOT_AUTHORIZED = 5, | ||||||
|   ESP8266_NOT_ENOUGH_SPACE = 6, |   ESP8266_NOT_ENOUGH_SPACE = 6, | ||||||
|   TLS_BAD_FINGERPRINT = 7 |   TLS_BAD_FINGERPRINT = 7, | ||||||
|  |   DNS_RESOLVE_ERROR = 8 | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /// internal struct for MQTT messages. | /// internal struct for MQTT messages. | ||||||
|   | |||||||
| @@ -176,7 +176,8 @@ void MQTTClientComponent::dump_config() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| bool MQTTClientComponent::can_proceed() { | bool MQTTClientComponent::can_proceed() { | ||||||
|   return network::is_disabled() || this->state_ == MQTT_CLIENT_DISABLED || this->is_connected(); |   return network::is_disabled() || this->state_ == MQTT_CLIENT_DISABLED || this->is_connected() || | ||||||
|  |          !this->wait_for_connection_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::start_dnslookup_() { | void MQTTClientComponent::start_dnslookup_() { | ||||||
| @@ -228,6 +229,8 @@ void MQTTClientComponent::check_dnslookup_() { | |||||||
|   if (this->dns_resolve_error_) { |   if (this->dns_resolve_error_) { | ||||||
|     ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str()); |     ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str()); | ||||||
|     this->state_ = MQTT_CLIENT_DISCONNECTED; |     this->state_ = MQTT_CLIENT_DISCONNECTED; | ||||||
|  |     this->disconnect_reason_ = MQTTClientDisconnectReason::DNS_RESOLVE_ERROR; | ||||||
|  |     this->on_disconnect_.call(MQTTClientDisconnectReason::DNS_RESOLVE_ERROR); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -697,7 +700,9 @@ void MQTTClientComponent::set_on_connect(mqtt_on_connect_callback_t &&callback) | |||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) { | void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) { | ||||||
|  |   auto callback_copy = callback; | ||||||
|   this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback)); |   this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback)); | ||||||
|  |   this->on_disconnect_.add(std::move(callback_copy)); | ||||||
| } | } | ||||||
|  |  | ||||||
| #if ASYNC_TCP_SSL_ENABLED | #if ASYNC_TCP_SSL_ENABLED | ||||||
|   | |||||||
| @@ -4,11 +4,12 @@ | |||||||
|  |  | ||||||
| #ifdef USE_MQTT | #ifdef USE_MQTT | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/core/automation.h" |  | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/components/json/json_util.h" | #include "esphome/components/json/json_util.h" | ||||||
| #include "esphome/components/network/ip_address.h" | #include "esphome/components/network/ip_address.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
| #if defined(USE_ESP32) | #if defined(USE_ESP32) | ||||||
| #include "mqtt_backend_esp32.h" | #include "mqtt_backend_esp32.h" | ||||||
| #elif defined(USE_ESP8266) | #elif defined(USE_ESP8266) | ||||||
| @@ -267,6 +268,8 @@ class MQTTClientComponent : public Component { | |||||||
|   void set_publish_nan_as_none(bool publish_nan_as_none); |   void set_publish_nan_as_none(bool publish_nan_as_none); | ||||||
|   bool is_publish_nan_as_none() const; |   bool is_publish_nan_as_none() const; | ||||||
|  |  | ||||||
|  |   void set_wait_for_connection(bool wait_for_connection) { this->wait_for_connection_ = wait_for_connection; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void send_device_info_(); |   void send_device_info_(); | ||||||
|  |  | ||||||
| @@ -332,8 +335,10 @@ class MQTTClientComponent : public Component { | |||||||
|   uint32_t connect_begin_; |   uint32_t connect_begin_; | ||||||
|   uint32_t last_connected_{0}; |   uint32_t last_connected_{0}; | ||||||
|   optional<MQTTClientDisconnectReason> disconnect_reason_{}; |   optional<MQTTClientDisconnectReason> disconnect_reason_{}; | ||||||
|  |   CallbackManager<MQTTBackend::on_disconnect_callback_t> on_disconnect_; | ||||||
|  |  | ||||||
|   bool publish_nan_as_none_{false}; |   bool publish_nan_as_none_{false}; | ||||||
|  |   bool wait_for_connection_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| extern MQTTClientComponent *global_mqtt_client;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | extern MQTTClientComponent *global_mqtt_client;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   | |||||||
| @@ -71,13 +71,13 @@ bool Nextion::check_connect_() { | |||||||
|     } |     } | ||||||
|     this->send_command_("connect"); |     this->send_command_("connect"); | ||||||
|  |  | ||||||
|     this->comok_sent_ = millis(); |     this->comok_sent_ = App.get_loop_component_start_time(); | ||||||
|     this->ignore_is_setup_ = false; |     this->ignore_is_setup_ = false; | ||||||
|  |  | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (millis() - this->comok_sent_ <= 500)  // Wait 500 ms |   if (App.get_loop_component_start_time() - this->comok_sent_ <= 500)  // Wait 500 ms | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   std::string response; |   std::string response; | ||||||
| @@ -318,15 +318,38 @@ void Nextion::loop() { | |||||||
|  |  | ||||||
|   if (!this->nextion_reports_is_setup_) { |   if (!this->nextion_reports_is_setup_) { | ||||||
|     if (this->started_ms_ == 0) |     if (this->started_ms_ == 0) | ||||||
|       this->started_ms_ = millis(); |       this->started_ms_ = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|     if (this->started_ms_ + this->startup_override_ms_ < millis()) { |     if (this->started_ms_ + this->startup_override_ms_ < App.get_loop_component_start_time()) { | ||||||
|       ESP_LOGD(TAG, "Manual ready set"); |       ESP_LOGD(TAG, "Manual ready set"); | ||||||
|       this->nextion_reports_is_setup_ = true; |       this->nextion_reports_is_setup_ = true; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | #ifdef USE_NEXTION_COMMAND_SPACING | ||||||
|  |   // Try to send any pending commands if spacing allows | ||||||
|  |   this->process_pending_in_queue_(); | ||||||
|  | #endif  // USE_NEXTION_COMMAND_SPACING | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifdef USE_NEXTION_COMMAND_SPACING | ||||||
|  | void Nextion::process_pending_in_queue_() { | ||||||
|  |   if (this->nextion_queue_.empty() || !this->command_pacer_.can_send()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Check if first item in queue has a pending command | ||||||
|  |   auto *front_item = this->nextion_queue_.front(); | ||||||
|  |   if (front_item && !front_item->pending_command.empty()) { | ||||||
|  |     if (this->send_command_(front_item->pending_command)) { | ||||||
|  |       // Command sent successfully, clear the pending command | ||||||
|  |       front_item->pending_command.clear(); | ||||||
|  |       ESP_LOGVV(TAG, "Pending command sent: %s", front_item->component->get_variable_name().c_str()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif  // USE_NEXTION_COMMAND_SPACING | ||||||
|  |  | ||||||
| bool Nextion::remove_from_q_(bool report_empty) { | bool Nextion::remove_from_q_(bool report_empty) { | ||||||
|   if (this->nextion_queue_.empty()) { |   if (this->nextion_queue_.empty()) { | ||||||
|     if (report_empty) { |     if (report_empty) { | ||||||
| @@ -409,7 +432,7 @@ void Nextion::process_nextion_commands_() { | |||||||
|       case 0x01:  // instruction sent by user was successful |       case 0x01:  // instruction sent by user was successful | ||||||
|  |  | ||||||
|         ESP_LOGVV(TAG, "Cmd OK"); |         ESP_LOGVV(TAG, "Cmd OK"); | ||||||
|         ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", this->nextion_queue_.empty() ? "True" : "False"); |         ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", YESNO(this->nextion_queue_.empty())); | ||||||
|  |  | ||||||
|         this->remove_from_q_(); |         this->remove_from_q_(); | ||||||
|         if (!this->is_setup_) { |         if (!this->is_setup_) { | ||||||
| @@ -421,7 +444,7 @@ void Nextion::process_nextion_commands_() { | |||||||
|         } |         } | ||||||
| #ifdef USE_NEXTION_COMMAND_SPACING | #ifdef USE_NEXTION_COMMAND_SPACING | ||||||
|         this->command_pacer_.mark_sent();  // Here is where we should mark the command as sent |         this->command_pacer_.mark_sent();  // Here is where we should mark the command as sent | ||||||
|         ESP_LOGN(TAG, "Command spacing: marked command sent at %u ms", millis()); |         ESP_LOGN(TAG, "Command spacing: marked command sent"); | ||||||
| #endif | #endif | ||||||
|         break; |         break; | ||||||
|       case 0x02:  // invalid Component ID or name was used |       case 0x02:  // invalid Component ID or name was used | ||||||
| @@ -805,7 +828,7 @@ void Nextion::process_nextion_commands_() { | |||||||
|     this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1); |     this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint32_t ms = millis(); |   uint32_t ms = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|   if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { |   if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { | ||||||
|     for (size_t i = 0; i < this->nextion_queue_.size(); i++) { |     for (size_t i = 0; i < this->nextion_queue_.size(); i++) { | ||||||
| @@ -940,11 +963,10 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool | |||||||
|   uint16_t ret = 0; |   uint16_t ret = 0; | ||||||
|   uint8_t c = 0; |   uint8_t c = 0; | ||||||
|   uint8_t nr_of_ff_bytes = 0; |   uint8_t nr_of_ff_bytes = 0; | ||||||
|   uint64_t start; |  | ||||||
|   bool exit_flag = false; |   bool exit_flag = false; | ||||||
|   bool ff_flag = false; |   bool ff_flag = false; | ||||||
|  |  | ||||||
|   start = millis(); |   const uint32_t start = millis(); | ||||||
|  |  | ||||||
|   while ((timeout == 0 && this->available()) || millis() - start <= timeout) { |   while ((timeout == 0 && this->available()) || millis() - start <= timeout) { | ||||||
|     if (!this->available()) { |     if (!this->available()) { | ||||||
| @@ -1034,9 +1056,42 @@ void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_n | |||||||
|  |  | ||||||
|   if (this->send_command_(command)) { |   if (this->send_command_(command)) { | ||||||
|     this->add_no_result_to_queue_(variable_name); |     this->add_no_result_to_queue_(variable_name); | ||||||
|  | #ifdef USE_NEXTION_COMMAND_SPACING | ||||||
|  |   } else { | ||||||
|  |     // Command blocked by spacing, add to queue WITH the command for retry | ||||||
|  |     this->add_no_result_to_queue_with_pending_command_(variable_name, command); | ||||||
|  | #endif  // USE_NEXTION_COMMAND_SPACING | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifdef USE_NEXTION_COMMAND_SPACING | ||||||
|  | void Nextion::add_no_result_to_queue_with_pending_command_(const std::string &variable_name, | ||||||
|  |                                                            const std::string &command) { | ||||||
|  | #ifdef USE_NEXTION_MAX_QUEUE_SIZE | ||||||
|  |   if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) { | ||||||
|  |     ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   RAMAllocator<nextion::NextionQueue> allocator; | ||||||
|  |   nextion::NextionQueue *nextion_queue = allocator.allocate(1); | ||||||
|  |   if (nextion_queue == nullptr) { | ||||||
|  |     ESP_LOGW(TAG, "Queue alloc failed"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   new (nextion_queue) nextion::NextionQueue(); | ||||||
|  |  | ||||||
|  |   nextion_queue->component = new nextion::NextionComponentBase; | ||||||
|  |   nextion_queue->component->set_variable_name(variable_name); | ||||||
|  |   nextion_queue->queue_time = App.get_loop_component_start_time(); | ||||||
|  |   nextion_queue->pending_command = command;  // Store command for retry | ||||||
|  |  | ||||||
|  |   this->nextion_queue_.push_back(nextion_queue); | ||||||
|  |   ESP_LOGVV(TAG, "Queue with pending command: %s", variable_name.c_str()); | ||||||
|  | } | ||||||
|  | #endif  // USE_NEXTION_COMMAND_SPACING | ||||||
|  |  | ||||||
| bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, | bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, | ||||||
|                                                                ...) { |                                                                ...) { | ||||||
|   if ((!this->is_setup() && !this->ignore_is_setup_)) |   if ((!this->is_setup() && !this->ignore_is_setup_)) | ||||||
| @@ -1168,7 +1223,7 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) { | |||||||
|   new (nextion_queue) nextion::NextionQueue(); |   new (nextion_queue) nextion::NextionQueue(); | ||||||
|  |  | ||||||
|   nextion_queue->component = component; |   nextion_queue->component = component; | ||||||
|   nextion_queue->queue_time = millis(); |   nextion_queue->queue_time = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|   ESP_LOGN(TAG, "Queue %s: %s", component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); |   ESP_LOGN(TAG, "Queue %s: %s", component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); | ||||||
|  |  | ||||||
| @@ -1200,7 +1255,7 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { | |||||||
|   new (nextion_queue) nextion::NextionQueue(); |   new (nextion_queue) nextion::NextionQueue(); | ||||||
|  |  | ||||||
|   nextion_queue->component = component; |   nextion_queue->component = component; | ||||||
|   nextion_queue->queue_time = millis(); |   nextion_queue->queue_time = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|   this->waveform_queue_.push_back(nextion_queue); |   this->waveform_queue_.push_back(nextion_queue); | ||||||
|   if (this->waveform_queue_.size() == 1) |   if (this->waveform_queue_.size() == 1) | ||||||
|   | |||||||
| @@ -1309,9 +1309,23 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | |||||||
| #ifdef USE_NEXTION_MAX_QUEUE_SIZE | #ifdef USE_NEXTION_MAX_QUEUE_SIZE | ||||||
|   size_t max_queue_size_{0}; |   size_t max_queue_size_{0}; | ||||||
| #endif  // USE_NEXTION_MAX_QUEUE_SIZE | #endif  // USE_NEXTION_MAX_QUEUE_SIZE | ||||||
|  |  | ||||||
| #ifdef USE_NEXTION_COMMAND_SPACING | #ifdef USE_NEXTION_COMMAND_SPACING | ||||||
|   NextionCommandPacer command_pacer_{0}; |   NextionCommandPacer command_pacer_{0}; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * @brief Process any commands in the queue that are pending due to command spacing | ||||||
|  |    * | ||||||
|  |    * This method checks if the first item in the nextion_queue_ has a pending command | ||||||
|  |    * that was previously blocked by command spacing. If spacing now allows and a | ||||||
|  |    * pending command exists, it attempts to send the command. Once successfully sent, | ||||||
|  |    * the pending command is cleared and the queue item continues normal processing. | ||||||
|  |    * | ||||||
|  |    * Called from loop() to retry sending commands that were delayed by spacing. | ||||||
|  |    */ | ||||||
|  |   void process_pending_in_queue_(); | ||||||
| #endif  // USE_NEXTION_COMMAND_SPACING | #endif  // USE_NEXTION_COMMAND_SPACING | ||||||
|  |  | ||||||
|   std::deque<NextionQueue *> nextion_queue_; |   std::deque<NextionQueue *> nextion_queue_; | ||||||
|   std::deque<NextionQueue *> waveform_queue_; |   std::deque<NextionQueue *> waveform_queue_; | ||||||
|   uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); |   uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); | ||||||
| @@ -1348,6 +1362,23 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe | |||||||
|       __attribute__((format(printf, 3, 4))); |       __attribute__((format(printf, 3, 4))); | ||||||
|   void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command); |   void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command); | ||||||
|  |  | ||||||
|  | #ifdef USE_NEXTION_COMMAND_SPACING | ||||||
|  |   /** | ||||||
|  |    * @brief Add a command to the Nextion queue with a pending command for retry | ||||||
|  |    * | ||||||
|  |    * This method creates a queue entry for a command that was blocked by command spacing. | ||||||
|  |    * The command string is stored in the queue item's pending_command field so it can | ||||||
|  |    * be retried later when spacing allows. This ensures commands are not lost when | ||||||
|  |    * sent too quickly. | ||||||
|  |    * | ||||||
|  |    * If the max_queue_size limit is configured and reached, the command will be dropped. | ||||||
|  |    * | ||||||
|  |    * @param variable_name Name of the variable or component associated with the command | ||||||
|  |    * @param command The actual command string to be sent when spacing allows | ||||||
|  |    */ | ||||||
|  |   void add_no_result_to_queue_with_pending_command_(const std::string &variable_name, const std::string &command); | ||||||
|  | #endif  // USE_NEXTION_COMMAND_SPACING | ||||||
|  |  | ||||||
|   bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) |   bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) | ||||||
|       __attribute__((format(printf, 3, 4))); |       __attribute__((format(printf, 3, 4))); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,6 +25,9 @@ class NextionQueue { | |||||||
|   virtual ~NextionQueue() = default; |   virtual ~NextionQueue() = default; | ||||||
|   NextionComponentBase *component; |   NextionComponentBase *component; | ||||||
|   uint32_t queue_time = 0; |   uint32_t queue_time = 0; | ||||||
|  |  | ||||||
|  |   // Store command for retry if spacing blocked it | ||||||
|  |   std::string pending_command;  // Empty if command was sent successfully | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class NextionComponentBase { | class NextionComponentBase { | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								esphome/components/nextion/nextion_upload.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/nextion/nextion_upload.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | #include "nextion.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_NEXTION_TFT_UPLOAD | ||||||
|  |  | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace nextion { | ||||||
|  | static const char *const TAG = "nextion.upload"; | ||||||
|  |  | ||||||
|  | bool Nextion::upload_end_(bool successful) { | ||||||
|  |   if (successful) { | ||||||
|  |     ESP_LOGD(TAG, "Upload successful"); | ||||||
|  |     delay(1500);  // NOLINT | ||||||
|  |     App.safe_reboot(); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "Upload failed"); | ||||||
|  |  | ||||||
|  |     this->is_updating_ = false; | ||||||
|  |     this->ignore_is_setup_ = false; | ||||||
|  |  | ||||||
|  |     uint32_t baud_rate = this->parent_->get_baud_rate(); | ||||||
|  |     if (baud_rate != this->original_baud_rate_) { | ||||||
|  |       ESP_LOGD(TAG, "Baud: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_); | ||||||
|  |       this->parent_->set_baud_rate(this->original_baud_rate_); | ||||||
|  |       this->parent_->load_settings(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return successful; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace nextion | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_NEXTION_TFT_UPLOAD | ||||||
| @@ -67,8 +67,8 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) { | |||||||
|     ESP_LOGV(TAG, "Fetch %" PRIu16 " bytes", buffer_size); |     ESP_LOGV(TAG, "Fetch %" PRIu16 " bytes", buffer_size); | ||||||
|     uint16_t read_len = 0; |     uint16_t read_len = 0; | ||||||
|     int partial_read_len = 0; |     int partial_read_len = 0; | ||||||
|     const uint32_t start_time = millis(); |     const uint32_t start_time = App.get_loop_component_start_time(); | ||||||
|     while (read_len < buffer_size && millis() - start_time < 5000) { |     while (read_len < buffer_size && App.get_loop_component_start_time() - start_time < 5000) { | ||||||
|       if (http_client.getStreamPtr()->available() > 0) { |       if (http_client.getStreamPtr()->available() > 0) { | ||||||
|         partial_read_len = |         partial_read_len = | ||||||
|             http_client.getStreamPtr()->readBytes(reinterpret_cast<char *>(buffer) + read_len, buffer_size - read_len); |             http_client.getStreamPtr()->readBytes(reinterpret_cast<char *>(buffer) + read_len, buffer_size - read_len); | ||||||
| @@ -335,31 +335,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { | |||||||
|   return upload_end_(true); |   return upload_end_(true); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Nextion::upload_end_(bool successful) { |  | ||||||
|   ESP_LOGD(TAG, "TFT upload done: %s", YESNO(successful)); |  | ||||||
|  |  | ||||||
|   if (successful) { |  | ||||||
|     ESP_LOGD(TAG, "Restart"); |  | ||||||
|     delay(1500);  // NOLINT |  | ||||||
|     App.safe_reboot(); |  | ||||||
|     delay(1500);  // NOLINT |  | ||||||
|   } else { |  | ||||||
|     ESP_LOGE(TAG, "TFT upload failed"); |  | ||||||
|  |  | ||||||
|     this->is_updating_ = false; |  | ||||||
|     this->ignore_is_setup_ = false; |  | ||||||
|  |  | ||||||
|     uint32_t baud_rate = this->parent_->get_baud_rate(); |  | ||||||
|     if (baud_rate != this->original_baud_rate_) { |  | ||||||
|       ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_); |  | ||||||
|       this->parent_->set_baud_rate(this->original_baud_rate_); |  | ||||||
|       this->parent_->load_settings(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return successful; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| WiFiClient *Nextion::get_wifi_client_() { | WiFiClient *Nextion::get_wifi_client_() { | ||||||
|   if (this->tft_url_.compare(0, 6, "https:") == 0) { |   if (this->tft_url_.compare(0, 6, "https:") == 0) { | ||||||
|   | |||||||
| @@ -335,30 +335,6 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) { | |||||||
|   return this->upload_end_(true); |   return this->upload_end_(true); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Nextion::upload_end_(bool successful) { |  | ||||||
|   ESP_LOGD(TAG, "TFT upload done: %s", YESNO(successful)); |  | ||||||
|  |  | ||||||
|   if (successful) { |  | ||||||
|     ESP_LOGD(TAG, "Restart"); |  | ||||||
|     delay(1500);  // NOLINT |  | ||||||
|     App.safe_reboot(); |  | ||||||
|   } else { |  | ||||||
|     ESP_LOGE(TAG, "TFT upload failed"); |  | ||||||
|  |  | ||||||
|     this->is_updating_ = false; |  | ||||||
|     this->ignore_is_setup_ = false; |  | ||||||
|  |  | ||||||
|     uint32_t baud_rate = this->parent_->get_baud_rate(); |  | ||||||
|     if (baud_rate != this->original_baud_rate_) { |  | ||||||
|       ESP_LOGD(TAG, "Baud back: %" PRIu32 "->%" PRIu32, baud_rate, this->original_baud_rate_); |  | ||||||
|       this->parent_->set_baud_rate(this->original_baud_rate_); |  | ||||||
|       this->parent_->load_settings(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return successful; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| }  // namespace nextion | }  // namespace nextion | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -76,8 +76,8 @@ from esphome.const import ( | |||||||
|     DEVICE_CLASS_WIND_SPEED, |     DEVICE_CLASS_WIND_SPEED, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
| @@ -207,6 +207,9 @@ _NUMBER_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def number_schema( | def number_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -237,7 +240,7 @@ NUMBER_SCHEMA.add_extra(cv.deprecated_schema_constant("number")) | |||||||
| async def setup_number_core_( | async def setup_number_core_( | ||||||
|     var, config, *, min_value: float, max_value: float, step: float |     var, config, *, min_value: float, max_value: float, step: float | ||||||
| ): | ): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "number") | ||||||
|  |  | ||||||
|     cg.add(var.traits.set_min_value(min_value)) |     cg.add(var.traits.set_min_value(min_value)) | ||||||
|     cg.add(var.traits.set_max_value(max_value)) |     cg.add(var.traits.set_max_value(max_value)) | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ MULTI_CONF = True | |||||||
|  |  | ||||||
| CONF_ON_DOWNLOAD_FINISHED = "on_download_finished" | CONF_ON_DOWNLOAD_FINISHED = "on_download_finished" | ||||||
| CONF_PLACEHOLDER = "placeholder" | CONF_PLACEHOLDER = "placeholder" | ||||||
|  | CONF_UPDATE = "update" | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -167,6 +168,7 @@ SET_URL_SCHEMA = cv.Schema( | |||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.use_id(OnlineImage), |         cv.GenerateID(): cv.use_id(OnlineImage), | ||||||
|         cv.Required(CONF_URL): cv.templatable(cv.url), |         cv.Required(CONF_URL): cv.templatable(cv.url), | ||||||
|  |         cv.Optional(CONF_UPDATE, default=True): cv.templatable(bool), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -188,6 +190,9 @@ async def online_image_action_to_code(config, action_id, template_arg, args): | |||||||
|     if CONF_URL in config: |     if CONF_URL in config: | ||||||
|         template_ = await cg.templatable(config[CONF_URL], args, cg.std_string) |         template_ = await cg.templatable(config[CONF_URL], args, cg.std_string) | ||||||
|         cg.add(var.set_url(template_)) |         cg.add(var.set_url(template_)) | ||||||
|  |     if CONF_UPDATE in config: | ||||||
|  |         template_ = await cg.templatable(config[CONF_UPDATE], args, bool) | ||||||
|  |         cg.add(var.set_update(template_)) | ||||||
|     return var |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -201,9 +201,12 @@ template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> { | |||||||
|  public: |  public: | ||||||
|   OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {} |   OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {} | ||||||
|   TEMPLATABLE_VALUE(std::string, url) |   TEMPLATABLE_VALUE(std::string, url) | ||||||
|  |   TEMPLATABLE_VALUE(bool, update) | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     this->parent_->set_url(this->url_.value(x...)); |     this->parent_->set_url(this->url_.value(x...)); | ||||||
|     this->parent_->update(); |     if (this->update_.value(x...)) { | ||||||
|  |       this->parent_->update(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ def set_sdkconfig_options(config): | |||||||
|     add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID]) |     add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID]) | ||||||
|     add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL]) |     add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL]) | ||||||
|     add_idf_sdkconfig_option( |     add_idf_sdkconfig_option( | ||||||
|         "CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}" |         "CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}".lower() | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     if network_name := config.get(CONF_NETWORK_NAME): |     if network_name := config.get(CONF_NETWORK_NAME): | ||||||
| @@ -54,14 +54,14 @@ def set_sdkconfig_options(config): | |||||||
|  |  | ||||||
|     if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None: |     if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None: | ||||||
|         add_idf_sdkconfig_option( |         add_idf_sdkconfig_option( | ||||||
|             "CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}" |             "CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower() | ||||||
|         ) |         ) | ||||||
|     if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None: |     if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None: | ||||||
|         add_idf_sdkconfig_option( |         add_idf_sdkconfig_option( | ||||||
|             "CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix:X}" |             "CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower() | ||||||
|         ) |         ) | ||||||
|     if (pskc := config.get(CONF_PSKC)) is not None: |     if (pskc := config.get(CONF_PSKC)) is not None: | ||||||
|         add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}") |         add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower()) | ||||||
|  |  | ||||||
|     if CONF_FORCE_DATASET in config: |     if CONF_FORCE_DATASET in config: | ||||||
|         if config[CONF_FORCE_DATASET]: |         if config[CONF_FORCE_DATASET]: | ||||||
| @@ -98,7 +98,7 @@ _CONNECTION_SCHEMA = cv.Schema( | |||||||
|         cv.Optional(CONF_EXT_PAN_ID): cv.hex_int, |         cv.Optional(CONF_EXT_PAN_ID): cv.hex_int, | ||||||
|         cv.Optional(CONF_NETWORK_NAME): cv.string_strict, |         cv.Optional(CONF_NETWORK_NAME): cv.string_strict, | ||||||
|         cv.Optional(CONF_PSKC): cv.hex_int, |         cv.Optional(CONF_PSKC): cv.hex_int, | ||||||
|         cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.hex_int, |         cv.Optional(CONF_MESH_LOCAL_PREFIX): cv.ipv6network, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -137,7 +137,7 @@ void OpenThreadSrpComponent::setup() { | |||||||
|   // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this |   // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this | ||||||
|   // component |   // component | ||||||
|   this->mdns_services_ = this->mdns_->get_services(); |   this->mdns_services_ = this->mdns_->get_services(); | ||||||
|   ESP_LOGW(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size()); |   ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size()); | ||||||
|   for (const auto &service : this->mdns_services_) { |   for (const auto &service : this->mdns_services_) { | ||||||
|     otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); |     otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); | ||||||
|     if (!entry) { |     if (!entry) { | ||||||
| @@ -185,11 +185,11 @@ void OpenThreadSrpComponent::setup() { | |||||||
|     if (error != OT_ERROR_NONE) { |     if (error != OT_ERROR_NONE) { | ||||||
|       ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error)); |       ESP_LOGW(TAG, "Failed to add service: %s", otThreadErrorToString(error)); | ||||||
|     } |     } | ||||||
|     ESP_LOGW(TAG, "Added service: %s", full_service.c_str()); |     ESP_LOGD(TAG, "Added service: %s", full_service.c_str()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr); |   otSrpClientEnableAutoStartMode(instance, srp_start_callback, nullptr); | ||||||
|   ESP_LOGW(TAG, "Finished SRP setup"); |   ESP_LOGD(TAG, "Finished SRP setup"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void *OpenThreadSrpComponent::pool_alloc_(size_t size) { | void *OpenThreadSrpComponent::pool_alloc_(size_t size) { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| # Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9 | # Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9 | ||||||
| import binascii | import binascii | ||||||
|  | import ipaddress | ||||||
|  |  | ||||||
| from esphome.const import CONF_CHANNEL | from esphome.const import CONF_CHANNEL | ||||||
|  |  | ||||||
| @@ -37,6 +38,12 @@ def parse_tlv(tlv) -> dict: | |||||||
|         if tag in TLV_TYPES: |         if tag in TLV_TYPES: | ||||||
|             if tag == 3: |             if tag == 3: | ||||||
|                 output[TLV_TYPES[tag]] = val.decode("utf-8") |                 output[TLV_TYPES[tag]] = val.decode("utf-8") | ||||||
|  |             elif tag == 7: | ||||||
|  |                 mesh_local_prefix = binascii.hexlify(val).decode("utf-8") | ||||||
|  |                 mesh_local_prefix_str = f"{mesh_local_prefix}0000000000000000" | ||||||
|  |                 ipv6_bytes = bytes.fromhex(mesh_local_prefix_str) | ||||||
|  |                 ipv6_address = ipaddress.IPv6Address(ipv6_bytes) | ||||||
|  |                 output[TLV_TYPES[tag]] = f"{ipv6_address}/64" | ||||||
|             else: |             else: | ||||||
|                 output[TLV_TYPES[tag]] = int.from_bytes(val) |                 output[TLV_TYPES[tag]] = int.from_bytes(val) | ||||||
|     return output |     return output | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/opt3001/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/opt3001/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										122
									
								
								esphome/components/opt3001/opt3001.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/opt3001/opt3001.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | #include "opt3001.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace opt3001 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "opt3001.sensor"; | ||||||
|  |  | ||||||
|  | static const uint8_t OPT3001_REG_RESULT = 0x00; | ||||||
|  | static const uint8_t OPT3001_REG_CONFIGURATION = 0x01; | ||||||
|  | // See datasheet for full description of each bit. | ||||||
|  | static const uint16_t OPT3001_CONFIGURATION_RANGE_FULL = 0b1100000000000000; | ||||||
|  | static const uint16_t OPT3001_CONFIGURATION_CONVERSION_TIME_800 = 0b100000000000; | ||||||
|  | static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_MASK = 0b11000000000; | ||||||
|  | static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT = 0b01000000000; | ||||||
|  | static const uint16_t OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN = 0b00000000000; | ||||||
|  | // tl;dr: Configure an automatic-ranged, 800ms single shot reading, | ||||||
|  | // with INT processing disabled | ||||||
|  | static const uint16_t OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT = OPT3001_CONFIGURATION_RANGE_FULL | | ||||||
|  |                                                                   OPT3001_CONFIGURATION_CONVERSION_TIME_800 | | ||||||
|  |                                                                   OPT3001_CONFIGURATION_CONVERSION_MODE_SINGLE_SHOT; | ||||||
|  | static const uint16_t OPT3001_CONVERSION_TIME_800 = 825;  // give it 25 extra ms; it seems to not be ready quite often | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | opt3001 properties: | ||||||
|  |  | ||||||
|  | - e (exponent) = high 4 bits of result register | ||||||
|  | - m (mantissa) = low 12 bits of result register | ||||||
|  | - formula: (0.01 * 2^e) * m lx | ||||||
|  |  | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | void OPT3001Sensor::read_result_(const std::function<void(float)> &f) { | ||||||
|  |   // ensure the single shot flag is clear, indicating it's done | ||||||
|  |   uint16_t raw_value; | ||||||
|  |   if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Reading configuration register failed"); | ||||||
|  |     f(NAN); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   raw_value = i2c::i2ctohs(raw_value); | ||||||
|  |  | ||||||
|  |   if ((raw_value & OPT3001_CONFIGURATION_CONVERSION_MODE_MASK) != OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN) { | ||||||
|  |     // not ready; wait 10ms and try again | ||||||
|  |     ESP_LOGW(TAG, "Data not ready; waiting 10ms"); | ||||||
|  |     this->set_timeout("opt3001_wait", 10, [this, f]() { read_result_(f); }); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->read_register(OPT3001_REG_RESULT, reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Reading result register failed"); | ||||||
|  |     f(NAN); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   raw_value = i2c::i2ctohs(raw_value); | ||||||
|  |  | ||||||
|  |   uint8_t exponent = raw_value >> 12; | ||||||
|  |   uint16_t mantissa = raw_value & 0b111111111111; | ||||||
|  |  | ||||||
|  |   double lx = 0.01 * pow(2.0, double(exponent)) * double(mantissa); | ||||||
|  |   f(float(lx)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void OPT3001Sensor::read_lx_(const std::function<void(float)> &f) { | ||||||
|  |   // turn on (after one-shot sensor automatically powers down) | ||||||
|  |   uint16_t start_measurement = i2c::htoi2cs(OPT3001_CONFIGURATION_FULL_RANGE_ONE_SHOT); | ||||||
|  |   if (this->write_register(OPT3001_REG_CONFIGURATION, reinterpret_cast<uint8_t *>(&start_measurement), 2) != | ||||||
|  |       i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGW(TAG, "Triggering one shot measurement failed"); | ||||||
|  |     f(NAN); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->set_timeout("read", OPT3001_CONVERSION_TIME_800, [this, f]() { | ||||||
|  |     if (this->write(&OPT3001_REG_CONFIGURATION, 1, true) != i2c::ERROR_OK) { | ||||||
|  |       ESP_LOGW(TAG, "Starting configuration register read failed"); | ||||||
|  |       f(NAN); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this->read_result_(f); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void OPT3001Sensor::dump_config() { | ||||||
|  |   LOG_SENSOR("", "OPT3001", this); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void OPT3001Sensor::update() { | ||||||
|  |   // Set a flag and skip just in case the sensor isn't responding, | ||||||
|  |   // and we just keep waiting for it in read_result_. | ||||||
|  |   // This way we don't end up with potentially boundless "threads" | ||||||
|  |   // using up memory and eventually crashing the device | ||||||
|  |   if (this->updating_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->updating_ = true; | ||||||
|  |  | ||||||
|  |   this->read_lx_([this](float val) { | ||||||
|  |     this->updating_ = false; | ||||||
|  |  | ||||||
|  |     if (std::isnan(val)) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       this->publish_state(NAN); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val); | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |     this->publish_state(val); | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float OPT3001Sensor::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | }  // namespace opt3001 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										27
									
								
								esphome/components/opt3001/opt3001.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/opt3001/opt3001.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace opt3001 { | ||||||
|  |  | ||||||
|  | /// This class implements support for the i2c-based OPT3001 ambient light sensor. | ||||||
|  | class OPT3001Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void dump_config() override; | ||||||
|  |   void update() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   // checks if one-shot is complete before reading the result and returning it | ||||||
|  |   void read_result_(const std::function<void(float)> &f); | ||||||
|  |   // begins a one-shot measurement | ||||||
|  |   void read_lx_(const std::function<void(float)> &f); | ||||||
|  |  | ||||||
|  |   bool updating_{false}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace opt3001 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										35
									
								
								esphome/components/opt3001/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/opt3001/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     DEVICE_CLASS_ILLUMINANCE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_LUX, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | CODEOWNERS = ["@ccutrer"] | ||||||
|  |  | ||||||
|  | opt3001_ns = cg.esphome_ns.namespace("opt3001") | ||||||
|  |  | ||||||
|  | OPT3001Sensor = opt3001_ns.class_( | ||||||
|  |     "OPT3001Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     sensor.sensor_schema( | ||||||
|  |         OPT3001Sensor, | ||||||
|  |         unit_of_measurement=UNIT_LUX, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_ILLUMINANCE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x44)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await sensor.new_sensor(config) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
| @@ -584,7 +584,7 @@ void PN7150::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_INIT); |         this->nci_fsm_set_state_(NCIState::NFCC_INIT); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_INIT: |     case NCIState::NFCC_INIT: | ||||||
|       if (this->init_core_() != nfc::STATUS_OK) { |       if (this->init_core_() != nfc::STATUS_OK) { | ||||||
| @@ -594,7 +594,7 @@ void PN7150::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_CONFIG); |         this->nci_fsm_set_state_(NCIState::NFCC_CONFIG); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_CONFIG: |     case NCIState::NFCC_CONFIG: | ||||||
|       if (this->send_init_config_() != nfc::STATUS_OK) { |       if (this->send_init_config_() != nfc::STATUS_OK) { | ||||||
| @@ -605,7 +605,7 @@ void PN7150::nci_fsm_transition_() { | |||||||
|         this->config_refresh_pending_ = false; |         this->config_refresh_pending_ = false; | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_SET_DISCOVER_MAP); |         this->nci_fsm_set_state_(NCIState::NFCC_SET_DISCOVER_MAP); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_SET_DISCOVER_MAP: |     case NCIState::NFCC_SET_DISCOVER_MAP: | ||||||
|       if (this->set_discover_map_() != nfc::STATUS_OK) { |       if (this->set_discover_map_() != nfc::STATUS_OK) { | ||||||
| @@ -615,7 +615,7 @@ void PN7150::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); |         this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_SET_LISTEN_MODE_ROUTING: |     case NCIState::NFCC_SET_LISTEN_MODE_ROUTING: | ||||||
|       if (this->set_listen_mode_routing_() != nfc::STATUS_OK) { |       if (this->set_listen_mode_routing_() != nfc::STATUS_OK) { | ||||||
| @@ -625,7 +625,7 @@ void PN7150::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::RFST_IDLE); |         this->nci_fsm_set_state_(NCIState::RFST_IDLE); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::RFST_IDLE: |     case NCIState::RFST_IDLE: | ||||||
|       if (this->nci_state_error_ == NCIState::RFST_DISCOVERY) { |       if (this->nci_state_error_ == NCIState::RFST_DISCOVERY) { | ||||||
| @@ -650,14 +650,14 @@ void PN7150::nci_fsm_transition_() { | |||||||
|  |  | ||||||
|     case NCIState::RFST_W4_HOST_SELECT: |     case NCIState::RFST_W4_HOST_SELECT: | ||||||
|       select_endpoint_(); |       select_endpoint_(); | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     // All cases below are waiting for NOTIFICATION messages |     // All cases below are waiting for NOTIFICATION messages | ||||||
|     case NCIState::RFST_DISCOVERY: |     case NCIState::RFST_DISCOVERY: | ||||||
|       if (this->config_refresh_pending_) { |       if (this->config_refresh_pending_) { | ||||||
|         this->refresh_core_config_(); |         this->refresh_core_config_(); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::RFST_LISTEN_ACTIVE: |     case NCIState::RFST_LISTEN_ACTIVE: | ||||||
|     case NCIState::RFST_LISTEN_SLEEP: |     case NCIState::RFST_LISTEN_SLEEP: | ||||||
|   | |||||||
| @@ -609,7 +609,7 @@ void PN7160::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_INIT); |         this->nci_fsm_set_state_(NCIState::NFCC_INIT); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_INIT: |     case NCIState::NFCC_INIT: | ||||||
|       if (this->init_core_() != nfc::STATUS_OK) { |       if (this->init_core_() != nfc::STATUS_OK) { | ||||||
| @@ -619,7 +619,7 @@ void PN7160::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_CONFIG); |         this->nci_fsm_set_state_(NCIState::NFCC_CONFIG); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_CONFIG: |     case NCIState::NFCC_CONFIG: | ||||||
|       if (this->send_init_config_() != nfc::STATUS_OK) { |       if (this->send_init_config_() != nfc::STATUS_OK) { | ||||||
| @@ -630,7 +630,7 @@ void PN7160::nci_fsm_transition_() { | |||||||
|         this->config_refresh_pending_ = false; |         this->config_refresh_pending_ = false; | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_SET_DISCOVER_MAP); |         this->nci_fsm_set_state_(NCIState::NFCC_SET_DISCOVER_MAP); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_SET_DISCOVER_MAP: |     case NCIState::NFCC_SET_DISCOVER_MAP: | ||||||
|       if (this->set_discover_map_() != nfc::STATUS_OK) { |       if (this->set_discover_map_() != nfc::STATUS_OK) { | ||||||
| @@ -640,7 +640,7 @@ void PN7160::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); |         this->nci_fsm_set_state_(NCIState::NFCC_SET_LISTEN_MODE_ROUTING); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::NFCC_SET_LISTEN_MODE_ROUTING: |     case NCIState::NFCC_SET_LISTEN_MODE_ROUTING: | ||||||
|       if (this->set_listen_mode_routing_() != nfc::STATUS_OK) { |       if (this->set_listen_mode_routing_() != nfc::STATUS_OK) { | ||||||
| @@ -650,7 +650,7 @@ void PN7160::nci_fsm_transition_() { | |||||||
|       } else { |       } else { | ||||||
|         this->nci_fsm_set_state_(NCIState::RFST_IDLE); |         this->nci_fsm_set_state_(NCIState::RFST_IDLE); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::RFST_IDLE: |     case NCIState::RFST_IDLE: | ||||||
|       if (this->nci_state_error_ == NCIState::RFST_DISCOVERY) { |       if (this->nci_state_error_ == NCIState::RFST_DISCOVERY) { | ||||||
| @@ -675,14 +675,14 @@ void PN7160::nci_fsm_transition_() { | |||||||
|  |  | ||||||
|     case NCIState::RFST_W4_HOST_SELECT: |     case NCIState::RFST_W4_HOST_SELECT: | ||||||
|       select_endpoint_(); |       select_endpoint_(); | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     // All cases below are waiting for NOTIFICATION messages |     // All cases below are waiting for NOTIFICATION messages | ||||||
|     case NCIState::RFST_DISCOVERY: |     case NCIState::RFST_DISCOVERY: | ||||||
|       if (this->config_refresh_pending_) { |       if (this->config_refresh_pending_) { | ||||||
|         this->refresh_core_config_(); |         this->refresh_core_config_(); | ||||||
|       } |       } | ||||||
|       // fall through |       [[fallthrough]]; | ||||||
|  |  | ||||||
|     case NCIState::RFST_LISTEN_ACTIVE: |     case NCIState::RFST_LISTEN_ACTIVE: | ||||||
|     case NCIState::RFST_LISTEN_SLEEP: |     case NCIState::RFST_LISTEN_SLEEP: | ||||||
|   | |||||||
| @@ -9,8 +9,8 @@ | |||||||
| #include <hardware/dma.h> | #include <hardware/dma.h> | ||||||
| #include <hardware/irq.h> | #include <hardware/irq.h> | ||||||
| #include <hardware/pio.h> | #include <hardware/pio.h> | ||||||
| #include <pico/stdlib.h> |  | ||||||
| #include <pico/sem.h> | #include <pico/sem.h> | ||||||
|  | #include <pico/stdlib.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace rp2040_pio_led_strip { | namespace rp2040_pio_led_strip { | ||||||
| @@ -44,7 +44,7 @@ void RP2040PIOLEDStripLightOutput::setup() { | |||||||
|  |  | ||||||
|   size_t buffer_size = this->get_buffer_size_(); |   size_t buffer_size = this->get_buffer_size_(); | ||||||
|  |  | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator; | ||||||
|   this->buf_ = allocator.allocate(buffer_size); |   this->buf_ = allocator.allocate(buffer_size); | ||||||
|   if (this->buf_ == nullptr) { |   if (this->buf_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Failed to allocate buffer of size %u", buffer_size); |     ESP_LOGE(TAG, "Failed to allocate buffer of size %u", buffer_size); | ||||||
|   | |||||||
| @@ -33,12 +33,15 @@ class SafeModeComponent : public Component { | |||||||
|   void write_rtc_(uint32_t val); |   void write_rtc_(uint32_t val); | ||||||
|   uint32_t read_rtc_(); |   uint32_t read_rtc_(); | ||||||
|  |  | ||||||
|   bool boot_successful_{false};                   ///< set to true after boot is considered successful |   // Group all 4-byte aligned members together to avoid padding | ||||||
|   uint32_t safe_mode_boot_is_good_after_{60000};  ///< The amount of time after which the boot is considered successful |   uint32_t safe_mode_boot_is_good_after_{60000};  ///< The amount of time after which the boot is considered successful | ||||||
|   uint32_t safe_mode_enable_time_{60000};         ///< The time safe mode should remain active for |   uint32_t safe_mode_enable_time_{60000};         ///< The time safe mode should remain active for | ||||||
|   uint32_t safe_mode_rtc_value_{0}; |   uint32_t safe_mode_rtc_value_{0}; | ||||||
|   uint32_t safe_mode_start_time_{0};  ///< stores when safe mode was enabled |   uint32_t safe_mode_start_time_{0};  ///< stores when safe mode was enabled | ||||||
|  |   // Group 1-byte members together to minimize padding | ||||||
|  |   bool boot_successful_{false};  ///< set to true after boot is considered successful | ||||||
|   uint8_t safe_mode_num_attempts_{0}; |   uint8_t safe_mode_num_attempts_{0}; | ||||||
|  |   // Larger objects at the end | ||||||
|   ESPPreferenceObject rtc_; |   ESPPreferenceObject rtc_; | ||||||
|   CallbackManager<void()> safe_mode_callback_{}; |   CallbackManager<void()> safe_mode_callback_{}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,8 +17,8 @@ from esphome.const import ( | |||||||
|     CONF_WEB_SERVER, |     CONF_WEB_SERVER, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
| @@ -65,6 +65,9 @@ _SELECT_SCHEMA = ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _SELECT_SCHEMA.add_extra(entity_duplicate_validator("select")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def select_schema( | def select_schema( | ||||||
|     class_: MockObjClass, |     class_: MockObjClass, | ||||||
|     *, |     *, | ||||||
| @@ -89,7 +92,7 @@ SELECT_SCHEMA.add_extra(cv.deprecated_schema_constant("select")) | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_select_core_(var, config, *, options: list[str]): | async def setup_select_core_(var, config, *, options: list[str]): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "select") | ||||||
|  |  | ||||||
|     cg.add(var.traits.set_options(options)) |     cg.add(var.traits.set_options(options)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -101,8 +101,8 @@ from esphome.const import ( | |||||||
|     ENTITY_CATEGORY_CONFIG, |     ENTITY_CATEGORY_CONFIG, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity | ||||||
| from esphome.cpp_generator import MockObjClass | from esphome.cpp_generator import MockObjClass | ||||||
| from esphome.cpp_helpers import setup_entity |  | ||||||
| from esphome.util import Registry | from esphome.util import Registry | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| @@ -318,6 +318,8 @@ _SENSOR_SCHEMA = ( | |||||||
|     ) |     ) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | _SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor")) | ||||||
|  |  | ||||||
|  |  | ||||||
| def sensor_schema( | def sensor_schema( | ||||||
|     class_: MockObjClass = cv.UNDEFINED, |     class_: MockObjClass = cv.UNDEFINED, | ||||||
| @@ -787,7 +789,7 @@ async def build_filters(config): | |||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_sensor_core_(var, config): | async def setup_sensor_core_(var, config): | ||||||
|     await setup_entity(var, config) |     await setup_entity(var, config, "sensor") | ||||||
|  |  | ||||||
|     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: |     if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: | ||||||
|         cg.add(var.set_device_class(device_class)) |         cg.add(var.set_device_class(device_class)) | ||||||
|   | |||||||
| @@ -23,16 +23,22 @@ std::string state_class_to_string(StateClass state_class) { | |||||||
| Sensor::Sensor() : state(NAN), raw_state(NAN) {} | Sensor::Sensor() : state(NAN), raw_state(NAN) {} | ||||||
|  |  | ||||||
| int8_t Sensor::get_accuracy_decimals() { | int8_t Sensor::get_accuracy_decimals() { | ||||||
|   if (this->accuracy_decimals_.has_value()) |   if (this->sensor_flags_.has_accuracy_override) | ||||||
|     return *this->accuracy_decimals_; |     return this->accuracy_decimals_; | ||||||
|   return 0; |   return 0; | ||||||
| } | } | ||||||
| void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } | void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { | ||||||
|  |   this->accuracy_decimals_ = accuracy_decimals; | ||||||
|  |   this->sensor_flags_.has_accuracy_override = true; | ||||||
|  | } | ||||||
|  |  | ||||||
| void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } | void Sensor::set_state_class(StateClass state_class) { | ||||||
|  |   this->state_class_ = state_class; | ||||||
|  |   this->sensor_flags_.has_state_class_override = true; | ||||||
|  | } | ||||||
| StateClass Sensor::get_state_class() { | StateClass Sensor::get_state_class() { | ||||||
|   if (this->state_class_.has_value()) |   if (this->sensor_flags_.has_state_class_override) | ||||||
|     return *this->state_class_; |     return this->state_class_; | ||||||
|   return StateClass::STATE_CLASS_NONE; |   return StateClass::STATE_CLASS_NONE; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -80,9 +80,9 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa | |||||||
|    * state changes to the database when they are published, even if the state is the |    * state changes to the database when they are published, even if the state is the | ||||||
|    * same as before. |    * same as before. | ||||||
|    */ |    */ | ||||||
|   bool get_force_update() const { return force_update_; } |   bool get_force_update() const { return sensor_flags_.force_update; } | ||||||
|   /// Set force update mode. |   /// Set force update mode. | ||||||
|   void set_force_update(bool force_update) { force_update_ = force_update; } |   void set_force_update(bool force_update) { sensor_flags_.force_update = force_update; } | ||||||
|  |  | ||||||
|   /// Add a filter to the filter chain. Will be appended to the back. |   /// Add a filter to the filter chain. Will be appended to the back. | ||||||
|   void add_filter(Filter *filter); |   void add_filter(Filter *filter); | ||||||
| @@ -155,9 +155,17 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa | |||||||
|  |  | ||||||
|   Filter *filter_list_{nullptr};  ///< Store all active filters. |   Filter *filter_list_{nullptr};  ///< Store all active filters. | ||||||
|  |  | ||||||
|   optional<int8_t> accuracy_decimals_;                  ///< Accuracy in decimals override |   // Group small members together to avoid padding | ||||||
|   optional<StateClass> state_class_{STATE_CLASS_NONE};  ///< State class override |   int8_t accuracy_decimals_{-1};              ///< Accuracy in decimals (-1 = not set) | ||||||
|   bool force_update_{false};                            ///< Force update mode |   StateClass state_class_{STATE_CLASS_NONE};  ///< State class (STATE_CLASS_NONE = not set) | ||||||
|  |  | ||||||
|  |   // Bit-packed flags for sensor-specific settings | ||||||
|  |   struct SensorFlags { | ||||||
|  |     uint8_t has_accuracy_override : 1; | ||||||
|  |     uint8_t has_state_class_override : 1; | ||||||
|  |     uint8_t force_update : 1; | ||||||
|  |     uint8_t reserved : 5;  // Reserved for future use | ||||||
|  |   } sensor_flags_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace sensor | }  // namespace sensor | ||||||
|   | |||||||
| @@ -445,8 +445,7 @@ template<typename T> stm32_err_t stm32_check_ack_timeout(const stm32_err_t s_err | |||||||
|       return STM32_ERR_OK; |       return STM32_ERR_OK; | ||||||
|     case STM32_ERR_NACK: |     case STM32_ERR_NACK: | ||||||
|       log(); |       log(); | ||||||
|       // TODO: c++17 [[fallthrough]] |       [[fallthrough]]; | ||||||
|       /* fallthrough */ |  | ||||||
|     default: |     default: | ||||||
|       return STM32_ERR_UNKNOWN; |       return STM32_ERR_UNKNOWN; | ||||||
|   } |   } | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user