mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into esp32_touch_isr
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -49,7 +49,7 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           python-version: "3.10" |           python-version: "3.10" | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.10.0 |         uses: docker/setup-buildx-action@v3.11.1 | ||||||
|  |  | ||||||
|       - name: Set TAG |       - name: Set TAG | ||||||
|         run: | |         run: | | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -99,7 +99,7 @@ jobs: | |||||||
|           python-version: "3.10" |           python-version: "3.10" | ||||||
|  |  | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.10.0 |         uses: docker/setup-buildx-action@v3.11.1 | ||||||
|  |  | ||||||
|       - name: Log in to docker hub |       - name: Log in to docker hub | ||||||
|         uses: docker/login-action@v3.4.0 |         uses: docker/login-action@v3.4.0 | ||||||
| @@ -178,7 +178,7 @@ jobs: | |||||||
|           merge-multiple: true |           merge-multiple: true | ||||||
|  |  | ||||||
|       - name: Set up Docker Buildx |       - name: Set up Docker Buildx | ||||||
|         uses: docker/setup-buildx-action@v3.10.0 |         uses: docker/setup-buildx-action@v3.11.1 | ||||||
|  |  | ||||||
|       - name: Log in to docker hub |       - name: Log in to docker hub | ||||||
|         if: matrix.registry == 'dockerhub' |         if: matrix.registry == 'dockerhub' | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/astral-sh/ruff-pre-commit |   - repo: https://github.com/astral-sh/ruff-pre-commit | ||||||
|     # Ruff version. |     # Ruff version. | ||||||
|     rev: v0.11.10 |     rev: v0.12.0 | ||||||
|     hooks: |     hooks: | ||||||
|       # Run the linter. |       # Run the linter. | ||||||
|       - id: ruff |       - id: ruff | ||||||
|   | |||||||
| @@ -520,6 +520,7 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl | |||||||
| esphome/components/xiaomi_mhoc303/* @drug123 | esphome/components/xiaomi_mhoc303/* @drug123 | ||||||
| esphome/components/xiaomi_mhoc401/* @vevsvevs | esphome/components/xiaomi_mhoc401/* @vevsvevs | ||||||
| esphome/components/xiaomi_rtcgq02lm/* @jesserockz | esphome/components/xiaomi_rtcgq02lm/* @jesserockz | ||||||
|  | esphome/components/xiaomi_xmwsdj04mmc/* @medusalix | ||||||
| esphome/components/xl9535/* @mreditor97 | esphome/components/xl9535/* @mreditor97 | ||||||
| esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68 | esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68 | ||||||
| esphome/components/xxtea/* @clydebarrow | esphome/components/xxtea/* @clydebarrow | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ from esphome.cpp_generator import (  # noqa: F401 | |||||||
|     TemplateArguments, |     TemplateArguments, | ||||||
|     add, |     add, | ||||||
|     add_build_flag, |     add_build_flag, | ||||||
|  |     add_build_unflag, | ||||||
|     add_define, |     add_define, | ||||||
|     add_global, |     add_global, | ||||||
|     add_library, |     add_library, | ||||||
| @@ -34,6 +35,7 @@ from esphome.cpp_generator import (  # noqa: F401 | |||||||
|     process_lambda, |     process_lambda, | ||||||
|     progmem_array, |     progmem_array, | ||||||
|     safe_exp, |     safe_exp, | ||||||
|  |     set_cpp_standard, | ||||||
|     statement, |     statement, | ||||||
|     static_const_array, |     static_const_array, | ||||||
|     templatable, |     templatable, | ||||||
|   | |||||||
| @@ -193,14 +193,13 @@ void AcDimmer::setup() { | |||||||
|   setTimer1Callback(&timer_interrupt); |   setTimer1Callback(&timer_interrupt); | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|   // 80 Divider -> 1 count=1µs |   // timer frequency of 1mhz | ||||||
|   dimmer_timer = timerBegin(0, 80, true); |   dimmer_timer = timerBegin(1000000); | ||||||
|   timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true); |   timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr); | ||||||
|   // For ESP32, we can't use dynamic interval calculation because the timerX functions |   // For ESP32, we can't use dynamic interval calculation because the timerX functions | ||||||
|   // are not callable from ISR (placed in flash storage). |   // are not callable from ISR (placed in flash storage). | ||||||
|   // Here we just use an interrupt firing every 50 µs. |   // Here we just use an interrupt firing every 50 µs. | ||||||
|   timerAlarmWrite(dimmer_timer, 50, true); |   timerAlarm(dimmer_timer, 50, true, 0); | ||||||
|   timerAlarmEnable(dimmer_timer); |  | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| void AcDimmer::write_state(float state) { | void AcDimmer::write_state(float state) { | ||||||
|   | |||||||
| @@ -17,7 +17,11 @@ void Anova::setup() { | |||||||
|   this->current_request_ = 0; |   this->current_request_ = 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Anova::loop() {} | void Anova::loop() { | ||||||
|  |   // Parent BLEClientNode has a loop() method, but this component uses | ||||||
|  |   // polling via update() and BLE callbacks so loop isn't needed | ||||||
|  |   this->disable_loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Anova::control(const ClimateCall &call) { | void Anova::control(const ClimateCall &call) { | ||||||
|   if (call.get_mode().has_value()) { |   if (call.get_mode().has_value()) { | ||||||
|   | |||||||
| @@ -266,6 +266,7 @@ enum EntityCategory { | |||||||
| // ==================== BINARY SENSOR ==================== | // ==================== BINARY SENSOR ==================== | ||||||
| message ListEntitiesBinarySensorResponse { | message ListEntitiesBinarySensorResponse { | ||||||
|   option (id) = 12; |   option (id) = 12; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_BINARY_SENSOR"; |   option (ifdef) = "USE_BINARY_SENSOR"; | ||||||
|  |  | ||||||
| @@ -282,6 +283,7 @@ message ListEntitiesBinarySensorResponse { | |||||||
| } | } | ||||||
| message BinarySensorStateResponse { | message BinarySensorStateResponse { | ||||||
|   option (id) = 21; |   option (id) = 21; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_BINARY_SENSOR"; |   option (ifdef) = "USE_BINARY_SENSOR"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -296,6 +298,7 @@ message BinarySensorStateResponse { | |||||||
| // ==================== COVER ==================== | // ==================== COVER ==================== | ||||||
| message ListEntitiesCoverResponse { | message ListEntitiesCoverResponse { | ||||||
|   option (id) = 13; |   option (id) = 13; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_COVER"; |   option (ifdef) = "USE_COVER"; | ||||||
|  |  | ||||||
| @@ -325,6 +328,7 @@ enum CoverOperation { | |||||||
| } | } | ||||||
| message CoverStateResponse { | message CoverStateResponse { | ||||||
|   option (id) = 22; |   option (id) = 22; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_COVER"; |   option (ifdef) = "USE_COVER"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -367,6 +371,7 @@ message CoverCommandRequest { | |||||||
| // ==================== FAN ==================== | // ==================== FAN ==================== | ||||||
| message ListEntitiesFanResponse { | message ListEntitiesFanResponse { | ||||||
|   option (id) = 14; |   option (id) = 14; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_FAN"; |   option (ifdef) = "USE_FAN"; | ||||||
|  |  | ||||||
| @@ -395,6 +400,7 @@ enum FanDirection { | |||||||
| } | } | ||||||
| message FanStateResponse { | message FanStateResponse { | ||||||
|   option (id) = 23; |   option (id) = 23; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_FAN"; |   option (ifdef) = "USE_FAN"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -444,6 +450,7 @@ enum ColorMode { | |||||||
| } | } | ||||||
| message ListEntitiesLightResponse { | message ListEntitiesLightResponse { | ||||||
|   option (id) = 15; |   option (id) = 15; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_LIGHT"; |   option (ifdef) = "USE_LIGHT"; | ||||||
|  |  | ||||||
| @@ -467,6 +474,7 @@ message ListEntitiesLightResponse { | |||||||
| } | } | ||||||
| message LightStateResponse { | message LightStateResponse { | ||||||
|   option (id) = 24; |   option (id) = 24; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_LIGHT"; |   option (ifdef) = "USE_LIGHT"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -536,6 +544,7 @@ enum SensorLastResetType { | |||||||
|  |  | ||||||
| message ListEntitiesSensorResponse { | message ListEntitiesSensorResponse { | ||||||
|   option (id) = 16; |   option (id) = 16; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SENSOR"; |   option (ifdef) = "USE_SENSOR"; | ||||||
|  |  | ||||||
| @@ -557,6 +566,7 @@ message ListEntitiesSensorResponse { | |||||||
| } | } | ||||||
| message SensorStateResponse { | message SensorStateResponse { | ||||||
|   option (id) = 25; |   option (id) = 25; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SENSOR"; |   option (ifdef) = "USE_SENSOR"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -571,6 +581,7 @@ message SensorStateResponse { | |||||||
| // ==================== SWITCH ==================== | // ==================== SWITCH ==================== | ||||||
| message ListEntitiesSwitchResponse { | message ListEntitiesSwitchResponse { | ||||||
|   option (id) = 17; |   option (id) = 17; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SWITCH"; |   option (ifdef) = "USE_SWITCH"; | ||||||
|  |  | ||||||
| @@ -587,6 +598,7 @@ message ListEntitiesSwitchResponse { | |||||||
| } | } | ||||||
| message SwitchStateResponse { | message SwitchStateResponse { | ||||||
|   option (id) = 26; |   option (id) = 26; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SWITCH"; |   option (ifdef) = "USE_SWITCH"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -607,6 +619,7 @@ message SwitchCommandRequest { | |||||||
| // ==================== TEXT SENSOR ==================== | // ==================== TEXT SENSOR ==================== | ||||||
| message ListEntitiesTextSensorResponse { | message ListEntitiesTextSensorResponse { | ||||||
|   option (id) = 18; |   option (id) = 18; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_TEXT_SENSOR"; |   option (ifdef) = "USE_TEXT_SENSOR"; | ||||||
|  |  | ||||||
| @@ -622,6 +635,7 @@ message ListEntitiesTextSensorResponse { | |||||||
| } | } | ||||||
| message TextSensorStateResponse { | message TextSensorStateResponse { | ||||||
|   option (id) = 27; |   option (id) = 27; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_TEXT_SENSOR"; |   option (ifdef) = "USE_TEXT_SENSOR"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -789,6 +803,7 @@ message ExecuteServiceRequest { | |||||||
| // ==================== CAMERA ==================== | // ==================== CAMERA ==================== | ||||||
| message ListEntitiesCameraResponse { | message ListEntitiesCameraResponse { | ||||||
|   option (id) = 43; |   option (id) = 43; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_ESP32_CAMERA"; |   option (ifdef) = "USE_ESP32_CAMERA"; | ||||||
|  |  | ||||||
| @@ -869,6 +884,7 @@ enum ClimatePreset { | |||||||
| } | } | ||||||
| message ListEntitiesClimateResponse { | message ListEntitiesClimateResponse { | ||||||
|   option (id) = 46; |   option (id) = 46; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_CLIMATE"; |   option (ifdef) = "USE_CLIMATE"; | ||||||
|  |  | ||||||
| @@ -903,6 +919,7 @@ message ListEntitiesClimateResponse { | |||||||
| } | } | ||||||
| message ClimateStateResponse { | message ClimateStateResponse { | ||||||
|   option (id) = 47; |   option (id) = 47; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_CLIMATE"; |   option (ifdef) = "USE_CLIMATE"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -964,6 +981,7 @@ enum NumberMode { | |||||||
| } | } | ||||||
| message ListEntitiesNumberResponse { | message ListEntitiesNumberResponse { | ||||||
|   option (id) = 49; |   option (id) = 49; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_NUMBER"; |   option (ifdef) = "USE_NUMBER"; | ||||||
|  |  | ||||||
| @@ -984,6 +1002,7 @@ message ListEntitiesNumberResponse { | |||||||
| } | } | ||||||
| message NumberStateResponse { | message NumberStateResponse { | ||||||
|   option (id) = 50; |   option (id) = 50; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_NUMBER"; |   option (ifdef) = "USE_NUMBER"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1007,6 +1026,7 @@ message NumberCommandRequest { | |||||||
| // ==================== SELECT ==================== | // ==================== SELECT ==================== | ||||||
| message ListEntitiesSelectResponse { | message ListEntitiesSelectResponse { | ||||||
|   option (id) = 52; |   option (id) = 52; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SELECT"; |   option (ifdef) = "USE_SELECT"; | ||||||
|  |  | ||||||
| @@ -1022,6 +1042,7 @@ message ListEntitiesSelectResponse { | |||||||
| } | } | ||||||
| message SelectStateResponse { | message SelectStateResponse { | ||||||
|   option (id) = 53; |   option (id) = 53; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SELECT"; |   option (ifdef) = "USE_SELECT"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1045,6 +1066,7 @@ message SelectCommandRequest { | |||||||
| // ==================== SIREN ==================== | // ==================== SIREN ==================== | ||||||
| message ListEntitiesSirenResponse { | message ListEntitiesSirenResponse { | ||||||
|   option (id) = 55; |   option (id) = 55; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SIREN"; |   option (ifdef) = "USE_SIREN"; | ||||||
|  |  | ||||||
| @@ -1062,6 +1084,7 @@ message ListEntitiesSirenResponse { | |||||||
| } | } | ||||||
| message SirenStateResponse { | message SirenStateResponse { | ||||||
|   option (id) = 56; |   option (id) = 56; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_SIREN"; |   option (ifdef) = "USE_SIREN"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1102,6 +1125,7 @@ enum LockCommand  { | |||||||
| } | } | ||||||
| message ListEntitiesLockResponse { | message ListEntitiesLockResponse { | ||||||
|   option (id) = 58; |   option (id) = 58; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_LOCK"; |   option (ifdef) = "USE_LOCK"; | ||||||
|  |  | ||||||
| @@ -1123,6 +1147,7 @@ message ListEntitiesLockResponse { | |||||||
| } | } | ||||||
| message LockStateResponse { | message LockStateResponse { | ||||||
|   option (id) = 59; |   option (id) = 59; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_LOCK"; |   option (ifdef) = "USE_LOCK"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1145,6 +1170,7 @@ message LockCommandRequest { | |||||||
| // ==================== BUTTON ==================== | // ==================== BUTTON ==================== | ||||||
| message ListEntitiesButtonResponse { | message ListEntitiesButtonResponse { | ||||||
|   option (id) = 61; |   option (id) = 61; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_BUTTON"; |   option (ifdef) = "USE_BUTTON"; | ||||||
|  |  | ||||||
| @@ -1196,6 +1222,7 @@ message MediaPlayerSupportedFormat { | |||||||
| } | } | ||||||
| message ListEntitiesMediaPlayerResponse { | message ListEntitiesMediaPlayerResponse { | ||||||
|   option (id) = 63; |   option (id) = 63; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_MEDIA_PLAYER"; |   option (ifdef) = "USE_MEDIA_PLAYER"; | ||||||
|  |  | ||||||
| @@ -1214,6 +1241,7 @@ message ListEntitiesMediaPlayerResponse { | |||||||
| } | } | ||||||
| message MediaPlayerStateResponse { | message MediaPlayerStateResponse { | ||||||
|   option (id) = 64; |   option (id) = 64; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_MEDIA_PLAYER"; |   option (ifdef) = "USE_MEDIA_PLAYER"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1615,6 +1643,7 @@ enum VoiceAssistantEvent { | |||||||
|   VOICE_ASSISTANT_STT_VAD_END = 12; |   VOICE_ASSISTANT_STT_VAD_END = 12; | ||||||
|   VOICE_ASSISTANT_TTS_STREAM_START = 98; |   VOICE_ASSISTANT_TTS_STREAM_START = 98; | ||||||
|   VOICE_ASSISTANT_TTS_STREAM_END = 99; |   VOICE_ASSISTANT_TTS_STREAM_END = 99; | ||||||
|  |   VOICE_ASSISTANT_INTENT_PROGRESS = 100; | ||||||
| } | } | ||||||
|  |  | ||||||
| message VoiceAssistantEventData { | message VoiceAssistantEventData { | ||||||
| @@ -1735,6 +1764,7 @@ enum AlarmControlPanelStateCommand { | |||||||
|  |  | ||||||
| message ListEntitiesAlarmControlPanelResponse { | message ListEntitiesAlarmControlPanelResponse { | ||||||
|   option (id) = 94; |   option (id) = 94; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_ALARM_CONTROL_PANEL"; |   option (ifdef) = "USE_ALARM_CONTROL_PANEL"; | ||||||
|  |  | ||||||
| @@ -1752,6 +1782,7 @@ message ListEntitiesAlarmControlPanelResponse { | |||||||
|  |  | ||||||
| message AlarmControlPanelStateResponse { | message AlarmControlPanelStateResponse { | ||||||
|   option (id) = 95; |   option (id) = 95; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_ALARM_CONTROL_PANEL"; |   option (ifdef) = "USE_ALARM_CONTROL_PANEL"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1776,6 +1807,7 @@ enum TextMode { | |||||||
| } | } | ||||||
| message ListEntitiesTextResponse { | message ListEntitiesTextResponse { | ||||||
|   option (id) = 97; |   option (id) = 97; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_TEXT"; |   option (ifdef) = "USE_TEXT"; | ||||||
|  |  | ||||||
| @@ -1794,6 +1826,7 @@ message ListEntitiesTextResponse { | |||||||
| } | } | ||||||
| message TextStateResponse { | message TextStateResponse { | ||||||
|   option (id) = 98; |   option (id) = 98; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_TEXT"; |   option (ifdef) = "USE_TEXT"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1818,6 +1851,7 @@ message TextCommandRequest { | |||||||
| // ==================== DATETIME DATE ==================== | // ==================== DATETIME DATE ==================== | ||||||
| message ListEntitiesDateResponse { | message ListEntitiesDateResponse { | ||||||
|   option (id) = 100; |   option (id) = 100; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_DATETIME_DATE"; |   option (ifdef) = "USE_DATETIME_DATE"; | ||||||
|  |  | ||||||
| @@ -1832,6 +1866,7 @@ message ListEntitiesDateResponse { | |||||||
| } | } | ||||||
| message DateStateResponse { | message DateStateResponse { | ||||||
|   option (id) = 101; |   option (id) = 101; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_DATETIME_DATE"; |   option (ifdef) = "USE_DATETIME_DATE"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1859,6 +1894,7 @@ message DateCommandRequest { | |||||||
| // ==================== DATETIME TIME ==================== | // ==================== DATETIME TIME ==================== | ||||||
| message ListEntitiesTimeResponse { | message ListEntitiesTimeResponse { | ||||||
|   option (id) = 103; |   option (id) = 103; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_DATETIME_TIME"; |   option (ifdef) = "USE_DATETIME_TIME"; | ||||||
|  |  | ||||||
| @@ -1873,6 +1909,7 @@ message ListEntitiesTimeResponse { | |||||||
| } | } | ||||||
| message TimeStateResponse { | message TimeStateResponse { | ||||||
|   option (id) = 104; |   option (id) = 104; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_DATETIME_TIME"; |   option (ifdef) = "USE_DATETIME_TIME"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1900,6 +1937,7 @@ message TimeCommandRequest { | |||||||
| // ==================== EVENT ==================== | // ==================== EVENT ==================== | ||||||
| message ListEntitiesEventResponse { | message ListEntitiesEventResponse { | ||||||
|   option (id) = 107; |   option (id) = 107; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_EVENT"; |   option (ifdef) = "USE_EVENT"; | ||||||
|  |  | ||||||
| @@ -1917,6 +1955,7 @@ message ListEntitiesEventResponse { | |||||||
| } | } | ||||||
| message EventResponse { | message EventResponse { | ||||||
|   option (id) = 108; |   option (id) = 108; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_EVENT"; |   option (ifdef) = "USE_EVENT"; | ||||||
|  |  | ||||||
| @@ -1927,6 +1966,7 @@ message EventResponse { | |||||||
| // ==================== VALVE ==================== | // ==================== VALVE ==================== | ||||||
| message ListEntitiesValveResponse { | message ListEntitiesValveResponse { | ||||||
|   option (id) = 109; |   option (id) = 109; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_VALVE"; |   option (ifdef) = "USE_VALVE"; | ||||||
|  |  | ||||||
| @@ -1952,6 +1992,7 @@ enum ValveOperation { | |||||||
| } | } | ||||||
| message ValveStateResponse { | message ValveStateResponse { | ||||||
|   option (id) = 110; |   option (id) = 110; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_VALVE"; |   option (ifdef) = "USE_VALVE"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -1976,6 +2017,7 @@ message ValveCommandRequest { | |||||||
| // ==================== DATETIME DATETIME ==================== | // ==================== DATETIME DATETIME ==================== | ||||||
| message ListEntitiesDateTimeResponse { | message ListEntitiesDateTimeResponse { | ||||||
|   option (id) = 112; |   option (id) = 112; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_DATETIME_DATETIME"; |   option (ifdef) = "USE_DATETIME_DATETIME"; | ||||||
|  |  | ||||||
| @@ -1990,6 +2032,7 @@ message ListEntitiesDateTimeResponse { | |||||||
| } | } | ||||||
| message DateTimeStateResponse { | message DateTimeStateResponse { | ||||||
|   option (id) = 113; |   option (id) = 113; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_DATETIME_DATETIME"; |   option (ifdef) = "USE_DATETIME_DATETIME"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
| @@ -2013,6 +2056,7 @@ message DateTimeCommandRequest { | |||||||
| // ==================== UPDATE ==================== | // ==================== UPDATE ==================== | ||||||
| message ListEntitiesUpdateResponse { | message ListEntitiesUpdateResponse { | ||||||
|   option (id) = 116; |   option (id) = 116; | ||||||
|  |   option (base_class) = "InfoResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_UPDATE"; |   option (ifdef) = "USE_UPDATE"; | ||||||
|  |  | ||||||
| @@ -2028,6 +2072,7 @@ message ListEntitiesUpdateResponse { | |||||||
| } | } | ||||||
| message UpdateStateResponse { | message UpdateStateResponse { | ||||||
|   option (id) = 117; |   option (id) = 117; | ||||||
|  |   option (base_class) = "StateResponseProtoMessage"; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
|   option (ifdef) = "USE_UPDATE"; |   option (ifdef) = "USE_UPDATE"; | ||||||
|   option (no_delay) = true; |   option (no_delay) = true; | ||||||
|   | |||||||
| @@ -61,8 +61,8 @@ void APIConnection::start() { | |||||||
|   APIError err = this->helper_->init(); |   APIError err = this->helper_->init(); | ||||||
|   if (err != APIError::OK) { |   if (err != APIError::OK) { | ||||||
|     on_fatal_error(); |     on_fatal_error(); | ||||||
|     ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), |     ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||||
|              errno); |              api_error_to_str(err), errno); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->client_info_ = helper_->getpeername(); |   this->client_info_ = helper_->getpeername(); | ||||||
| @@ -91,7 +91,7 @@ void APIConnection::loop() { | |||||||
|     // when network is disconnected force disconnect immediately |     // when network is disconnected force disconnect immediately | ||||||
|     // don't wait for timeout |     // don't wait for timeout | ||||||
|     this->on_fatal_error(); |     this->on_fatal_error(); | ||||||
|     ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->client_combined_info_.c_str()); |     ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->get_client_combined_info().c_str()); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->next_close_) { |   if (this->next_close_) { | ||||||
| @@ -104,7 +104,7 @@ void APIConnection::loop() { | |||||||
|   APIError err = this->helper_->loop(); |   APIError err = this->helper_->loop(); | ||||||
|   if (err != APIError::OK) { |   if (err != APIError::OK) { | ||||||
|     on_fatal_error(); |     on_fatal_error(); | ||||||
|     ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(), |     ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||||
|              api_error_to_str(err), errno); |              api_error_to_str(err), errno); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -118,12 +118,12 @@ void APIConnection::loop() { | |||||||
|     } else if (err != APIError::OK) { |     } else if (err != APIError::OK) { | ||||||
|       on_fatal_error(); |       on_fatal_error(); | ||||||
|       if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { |       if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { | ||||||
|         ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str()); |         ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); | ||||||
|       } else if (err == APIError::CONNECTION_CLOSED) { |       } else if (err == APIError::CONNECTION_CLOSED) { | ||||||
|         ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str()); |         ESP_LOGW(TAG, "%s: Connection closed", this->get_client_combined_info().c_str()); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), |         ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||||
|                  errno); |                  api_error_to_str(err), errno); | ||||||
|       } |       } | ||||||
|       return; |       return; | ||||||
|     } else { |     } else { | ||||||
| @@ -157,7 +157,7 @@ void APIConnection::loop() { | |||||||
|     // 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_TIMEOUT_MS * 5) / 2) { | ||||||
|       on_fatal_error(); |       on_fatal_error(); | ||||||
|       ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->client_combined_info_.c_str()); |       ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); | ||||||
|     } |     } | ||||||
|   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) { |   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) { | ||||||
|     ESP_LOGVV(TAG, "Sending keepalive PING"); |     ESP_LOGVV(TAG, "Sending keepalive PING"); | ||||||
| @@ -166,7 +166,7 @@ void APIConnection::loop() { | |||||||
|       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);", |       std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);", | ||||||
|                                          this->client_combined_info_.c_str(), this->ping_retries_); |                                          this->get_client_combined_info().c_str(), this->ping_retries_); | ||||||
|       if (this->ping_retries_ >= max_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 disconnecting", warn_str.c_str()); | ||||||
| @@ -233,7 +233,7 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { | |||||||
|   // remote initiated disconnect_client |   // remote initiated disconnect_client | ||||||
|   // don't close yet, we still need to send the disconnect response |   // don't close yet, we still need to send the disconnect response | ||||||
|   // close will happen on next loop |   // close will happen on next loop | ||||||
|   ESP_LOGD(TAG, "%s disconnected", this->client_combined_info_.c_str()); |   ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str()); | ||||||
|   this->next_close_ = true; |   this->next_close_ = true; | ||||||
|   DisconnectResponse resp; |   DisconnectResponse resp; | ||||||
|   return resp; |   return resp; | ||||||
| @@ -248,25 +248,41 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) { | |||||||
| uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, | uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, | ||||||
|                                                  uint32_t remaining_size, bool is_single) { |                                                  uint32_t remaining_size, bool is_single) { | ||||||
|   // Calculate size |   // Calculate size | ||||||
|   uint32_t size = 0; |   uint32_t calculated_size = 0; | ||||||
|   msg.calculate_size(size); |   msg.calculate_size(calculated_size); | ||||||
|  |  | ||||||
|  |   // Cache frame sizes to avoid repeated virtual calls | ||||||
|  |   const uint8_t header_padding = conn->helper_->frame_header_padding(); | ||||||
|  |   const uint8_t footer_size = conn->helper_->frame_footer_size(); | ||||||
|  |  | ||||||
|   // Calculate total size with padding for buffer allocation |   // Calculate total size with padding for buffer allocation | ||||||
|   uint16_t total_size = |   size_t total_calculated_size = calculated_size + header_padding + footer_size; | ||||||
|       static_cast<uint16_t>(size) + conn->helper_->frame_header_padding() + conn->helper_->frame_footer_size(); |  | ||||||
|  |  | ||||||
|   // Check if it fits |   // Check if it fits | ||||||
|   if (total_size > remaining_size) { |   if (total_calculated_size > remaining_size) { | ||||||
|     return 0;  // Doesn't fit |     return 0;  // Doesn't fit | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Allocate exact buffer space needed (just the payload, not the overhead) |   // Allocate buffer space - pass payload size, allocation functions add header/footer space | ||||||
|   ProtoWriteBuffer buffer = |   ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size) | ||||||
|       is_single ? conn->allocate_single_message_buffer(size) : conn->allocate_batch_message_buffer(size); |                                       : conn->allocate_batch_message_buffer(calculated_size); | ||||||
|  |  | ||||||
|  |   // Get buffer size after allocation (which includes header padding) | ||||||
|  |   std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref(); | ||||||
|  |   size_t size_before_encode = shared_buf.size(); | ||||||
|  |  | ||||||
|   // Encode directly into buffer |   // Encode directly into buffer | ||||||
|   msg.encode(buffer); |   msg.encode(buffer); | ||||||
|   return total_size; |  | ||||||
|  |   // Calculate actual encoded size (not including header that was already added) | ||||||
|  |   size_t actual_payload_size = shared_buf.size() - size_before_encode; | ||||||
|  |  | ||||||
|  |   // Return actual total size (header + actual payload + footer) | ||||||
|  |   size_t actual_total_size = header_padding + actual_payload_size + footer_size; | ||||||
|  |  | ||||||
|  |   // Verify that calculate_size() returned the correct value | ||||||
|  |   assert(calculated_size == actual_payload_size); | ||||||
|  |   return static_cast<uint16_t>(actual_total_size); | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| @@ -285,7 +301,7 @@ uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConn | |||||||
|   BinarySensorStateResponse resp; |   BinarySensorStateResponse resp; | ||||||
|   resp.state = binary_sensor->state; |   resp.state = binary_sensor->state; | ||||||
|   resp.missing_state = !binary_sensor->has_state(); |   resp.missing_state = !binary_sensor->has_state(); | ||||||
|   resp.key = binary_sensor->get_object_id_hash(); |   fill_entity_state_base(binary_sensor, resp); | ||||||
|   return encode_message_to_buffer(resp, BinarySensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, BinarySensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -319,7 +335,7 @@ uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection * | |||||||
|   if (traits.get_supports_tilt()) |   if (traits.get_supports_tilt()) | ||||||
|     msg.tilt = cover->tilt; |     msg.tilt = cover->tilt; | ||||||
|   msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation); |   msg.current_operation = static_cast<enums::CoverOperation>(cover->current_operation); | ||||||
|   msg.key = cover->get_object_id_hash(); |   fill_entity_state_base(cover, msg); | ||||||
|   return encode_message_to_buffer(msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(msg, CoverStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -387,7 +403,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co | |||||||
|     msg.direction = static_cast<enums::FanDirection>(fan->direction); |     msg.direction = static_cast<enums::FanDirection>(fan->direction); | ||||||
|   if (traits.supports_preset_modes()) |   if (traits.supports_preset_modes()) | ||||||
|     msg.preset_mode = fan->preset_mode; |     msg.preset_mode = fan->preset_mode; | ||||||
|   msg.key = fan->get_object_id_hash(); |   fill_entity_state_base(fan, msg); | ||||||
|   return encode_message_to_buffer(msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -454,7 +470,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection * | |||||||
|   resp.warm_white = values.get_warm_white(); |   resp.warm_white = values.get_warm_white(); | ||||||
|   if (light->supports_effects()) |   if (light->supports_effects()) | ||||||
|     resp.effect = light->get_effect_name(); |     resp.effect = light->get_effect_name(); | ||||||
|   resp.key = light->get_object_id_hash(); |   fill_entity_state_base(light, resp); | ||||||
|   return encode_message_to_buffer(resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -536,7 +552,7 @@ uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection | |||||||
|   SensorStateResponse resp; |   SensorStateResponse resp; | ||||||
|   resp.state = sensor->state; |   resp.state = sensor->state; | ||||||
|   resp.missing_state = !sensor->has_state(); |   resp.missing_state = !sensor->has_state(); | ||||||
|   resp.key = sensor->get_object_id_hash(); |   fill_entity_state_base(sensor, resp); | ||||||
|   return encode_message_to_buffer(resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, SensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -570,7 +586,7 @@ uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection | |||||||
|   auto *a_switch = static_cast<switch_::Switch *>(entity); |   auto *a_switch = static_cast<switch_::Switch *>(entity); | ||||||
|   SwitchStateResponse resp; |   SwitchStateResponse resp; | ||||||
|   resp.state = a_switch->state; |   resp.state = a_switch->state; | ||||||
|   resp.key = a_switch->get_object_id_hash(); |   fill_entity_state_base(a_switch, resp); | ||||||
|   return encode_message_to_buffer(resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, SwitchStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -613,7 +629,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec | |||||||
|   TextSensorStateResponse resp; |   TextSensorStateResponse resp; | ||||||
|   resp.state = text_sensor->state; |   resp.state = text_sensor->state; | ||||||
|   resp.missing_state = !text_sensor->has_state(); |   resp.missing_state = !text_sensor->has_state(); | ||||||
|   resp.key = text_sensor->get_object_id_hash(); |   fill_entity_state_base(text_sensor, resp); | ||||||
|   return encode_message_to_buffer(resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -637,7 +653,7 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection | |||||||
|                                                bool is_single) { |                                                bool is_single) { | ||||||
|   auto *climate = static_cast<climate::Climate *>(entity); |   auto *climate = static_cast<climate::Climate *>(entity); | ||||||
|   ClimateStateResponse resp; |   ClimateStateResponse resp; | ||||||
|   resp.key = climate->get_object_id_hash(); |   fill_entity_state_base(climate, resp); | ||||||
|   auto traits = climate->get_traits(); |   auto traits = climate->get_traits(); | ||||||
|   resp.mode = static_cast<enums::ClimateMode>(climate->mode); |   resp.mode = static_cast<enums::ClimateMode>(climate->mode); | ||||||
|   resp.action = static_cast<enums::ClimateAction>(climate->action); |   resp.action = static_cast<enums::ClimateAction>(climate->action); | ||||||
| @@ -746,7 +762,7 @@ uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection | |||||||
|   NumberStateResponse resp; |   NumberStateResponse resp; | ||||||
|   resp.state = number->state; |   resp.state = number->state; | ||||||
|   resp.missing_state = !number->has_state(); |   resp.missing_state = !number->has_state(); | ||||||
|   resp.key = number->get_object_id_hash(); |   fill_entity_state_base(number, resp); | ||||||
|   return encode_message_to_buffer(resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, NumberStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -787,7 +803,7 @@ uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *c | |||||||
|   resp.year = date->year; |   resp.year = date->year; | ||||||
|   resp.month = date->month; |   resp.month = date->month; | ||||||
|   resp.day = date->day; |   resp.day = date->day; | ||||||
|   resp.key = date->get_object_id_hash(); |   fill_entity_state_base(date, resp); | ||||||
|   return encode_message_to_buffer(resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, DateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| void APIConnection::send_date_info(datetime::DateEntity *date) { | void APIConnection::send_date_info(datetime::DateEntity *date) { | ||||||
| @@ -824,7 +840,7 @@ uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *c | |||||||
|   resp.hour = time->hour; |   resp.hour = time->hour; | ||||||
|   resp.minute = time->minute; |   resp.minute = time->minute; | ||||||
|   resp.second = time->second; |   resp.second = time->second; | ||||||
|   resp.key = time->get_object_id_hash(); |   fill_entity_state_base(time, resp); | ||||||
|   return encode_message_to_buffer(resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, TimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| void APIConnection::send_time_info(datetime::TimeEntity *time) { | void APIConnection::send_time_info(datetime::TimeEntity *time) { | ||||||
| @@ -863,7 +879,7 @@ uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnectio | |||||||
|     ESPTime state = datetime->state_as_esptime(); |     ESPTime state = datetime->state_as_esptime(); | ||||||
|     resp.epoch_seconds = state.timestamp; |     resp.epoch_seconds = state.timestamp; | ||||||
|   } |   } | ||||||
|   resp.key = datetime->get_object_id_hash(); |   fill_entity_state_base(datetime, resp); | ||||||
|   return encode_message_to_buffer(resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, DateTimeStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | void APIConnection::send_datetime_info(datetime::DateTimeEntity *datetime) { | ||||||
| @@ -902,7 +918,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c | |||||||
|   TextStateResponse resp; |   TextStateResponse resp; | ||||||
|   resp.state = text->state; |   resp.state = text->state; | ||||||
|   resp.missing_state = !text->has_state(); |   resp.missing_state = !text->has_state(); | ||||||
|   resp.key = text->get_object_id_hash(); |   fill_entity_state_base(text, resp); | ||||||
|   return encode_message_to_buffer(resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -943,7 +959,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection | |||||||
|   SelectStateResponse resp; |   SelectStateResponse resp; | ||||||
|   resp.state = select->state; |   resp.state = select->state; | ||||||
|   resp.missing_state = !select->has_state(); |   resp.missing_state = !select->has_state(); | ||||||
|   resp.key = select->get_object_id_hash(); |   fill_entity_state_base(select, resp); | ||||||
|   return encode_message_to_buffer(resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1003,7 +1019,7 @@ uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *c | |||||||
|   auto *a_lock = static_cast<lock::Lock *>(entity); |   auto *a_lock = static_cast<lock::Lock *>(entity); | ||||||
|   LockStateResponse resp; |   LockStateResponse resp; | ||||||
|   resp.state = static_cast<enums::LockState>(a_lock->state); |   resp.state = static_cast<enums::LockState>(a_lock->state); | ||||||
|   resp.key = a_lock->get_object_id_hash(); |   fill_entity_state_base(a_lock, resp); | ||||||
|   return encode_message_to_buffer(resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, LockStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1047,7 +1063,7 @@ uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection * | |||||||
|   ValveStateResponse resp; |   ValveStateResponse resp; | ||||||
|   resp.position = valve->position; |   resp.position = valve->position; | ||||||
|   resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation); |   resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation); | ||||||
|   resp.key = valve->get_object_id_hash(); |   fill_entity_state_base(valve, resp); | ||||||
|   return encode_message_to_buffer(resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, ValveStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| void APIConnection::send_valve_info(valve::Valve *valve) { | void APIConnection::send_valve_info(valve::Valve *valve) { | ||||||
| @@ -1095,7 +1111,7 @@ uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConne | |||||||
|   resp.state = static_cast<enums::MediaPlayerState>(report_state); |   resp.state = static_cast<enums::MediaPlayerState>(report_state); | ||||||
|   resp.volume = media_player->volume; |   resp.volume = media_player->volume; | ||||||
|   resp.muted = media_player->is_muted(); |   resp.muted = media_player->is_muted(); | ||||||
|   resp.key = media_player->get_object_id_hash(); |   fill_entity_state_base(media_player, resp); | ||||||
|   return encode_message_to_buffer(resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, MediaPlayerStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { | void APIConnection::send_media_player_info(media_player::MediaPlayer *media_player) { | ||||||
| @@ -1359,7 +1375,7 @@ uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, A | |||||||
|   auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity); |   auto *a_alarm_control_panel = static_cast<alarm_control_panel::AlarmControlPanel *>(entity); | ||||||
|   AlarmControlPanelStateResponse resp; |   AlarmControlPanelStateResponse resp; | ||||||
|   resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state()); |   resp.state = static_cast<enums::AlarmControlPanelState>(a_alarm_control_panel->get_state()); | ||||||
|   resp.key = a_alarm_control_panel->get_object_id_hash(); |   fill_entity_state_base(a_alarm_control_panel, resp); | ||||||
|   return encode_message_to_buffer(resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, AlarmControlPanelStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | void APIConnection::send_alarm_control_panel_info(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||||
| @@ -1423,7 +1439,7 @@ uint16_t APIConnection::try_send_event_response(event::Event *event, const std:: | |||||||
|                                                 uint32_t remaining_size, bool is_single) { |                                                 uint32_t remaining_size, bool is_single) { | ||||||
|   EventResponse resp; |   EventResponse resp; | ||||||
|   resp.event_type = event_type; |   resp.event_type = event_type; | ||||||
|   resp.key = event->get_object_id_hash(); |   fill_entity_state_base(event, resp); | ||||||
|   return encode_message_to_buffer(resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1461,7 +1477,7 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection | |||||||
|     resp.release_summary = update->update_info.summary; |     resp.release_summary = update->update_info.summary; | ||||||
|     resp.release_url = update->update_info.release_url; |     resp.release_url = update->update_info.release_url; | ||||||
|   } |   } | ||||||
|   resp.key = update->get_object_id_hash(); |   fill_entity_state_base(update, resp); | ||||||
|   return encode_message_to_buffer(resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); |   return encode_message_to_buffer(resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); | ||||||
| } | } | ||||||
| void APIConnection::send_update_info(update::UpdateEntity *update) { | void APIConnection::send_update_info(update::UpdateEntity *update) { | ||||||
| @@ -1522,14 +1538,13 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char | |||||||
|   buffer.encode_string(3, line, line_length);             // string message = 3 |   buffer.encode_string(3, line, line_length);             // string message = 3 | ||||||
|  |  | ||||||
|   // SubscribeLogsResponse - 29 |   // SubscribeLogsResponse - 29 | ||||||
|   return this->send_buffer(buffer, 29); |   return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| HelloResponse APIConnection::hello(const HelloRequest &msg) { | HelloResponse APIConnection::hello(const HelloRequest &msg) { | ||||||
|   this->client_info_ = msg.client_info; |   this->client_info_ = msg.client_info; | ||||||
|   this->client_peername_ = this->helper_->getpeername(); |   this->client_peername_ = this->helper_->getpeername(); | ||||||
|   this->client_combined_info_ = this->client_info_ + " (" + this->client_peername_ + ")"; |   this->helper_->set_log_info(this->get_client_combined_info()); | ||||||
|   this->helper_->set_log_info(this->client_combined_info_); |  | ||||||
|   this->client_api_version_major_ = msg.api_version_major; |   this->client_api_version_major_ = msg.api_version_major; | ||||||
|   this->client_api_version_minor_ = msg.api_version_minor; |   this->client_api_version_minor_ = msg.api_version_minor; | ||||||
|   ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), |   ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), | ||||||
| @@ -1551,7 +1566,7 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { | |||||||
|   // bool invalid_password = 1; |   // bool invalid_password = 1; | ||||||
|   resp.invalid_password = !correct; |   resp.invalid_password = !correct; | ||||||
|   if (correct) { |   if (correct) { | ||||||
|     ESP_LOGD(TAG, "%s connected", this->client_combined_info_.c_str()); |     ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); | ||||||
|     this->connection_state_ = ConnectionState::AUTHENTICATED; |     this->connection_state_ = ConnectionState::AUTHENTICATED; | ||||||
|     this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); |     this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); | ||||||
| #ifdef USE_HOMEASSISTANT_TIME | #ifdef USE_HOMEASSISTANT_TIME | ||||||
| @@ -1657,7 +1672,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | |||||||
|   APIError err = this->helper_->loop(); |   APIError err = this->helper_->loop(); | ||||||
|   if (err != APIError::OK) { |   if (err != APIError::OK) { | ||||||
|     on_fatal_error(); |     on_fatal_error(); | ||||||
|     ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(), |     ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->get_client_combined_info().c_str(), | ||||||
|              api_error_to_str(err), errno); |              api_error_to_str(err), errno); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| @@ -1669,7 +1684,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | |||||||
|   return false; |   return false; | ||||||
| } | } | ||||||
| bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) { | bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) { | ||||||
|   if (!this->try_to_clear_buffer(message_type != 29)) {  // SubscribeLogsResponse |   if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) {  // SubscribeLogsResponse | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -1679,10 +1694,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) | |||||||
|   if (err != APIError::OK) { |   if (err != APIError::OK) { | ||||||
|     on_fatal_error(); |     on_fatal_error(); | ||||||
|     if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { |     if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { | ||||||
|       ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str()); |       ESP_LOGW(TAG, "%s: Connection reset", this->get_client_combined_info().c_str()); | ||||||
|     } else { |     } else { | ||||||
|       ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), |       ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->get_client_combined_info().c_str(), | ||||||
|                errno); |                api_error_to_str(err), errno); | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| @@ -1691,11 +1706,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) | |||||||
| } | } | ||||||
| void APIConnection::on_unauthenticated_access() { | void APIConnection::on_unauthenticated_access() { | ||||||
|   this->on_fatal_error(); |   this->on_fatal_error(); | ||||||
|   ESP_LOGD(TAG, "%s requested access without authentication", this->client_combined_info_.c_str()); |   ESP_LOGD(TAG, "%s requested access without authentication", this->get_client_combined_info().c_str()); | ||||||
| } | } | ||||||
| void APIConnection::on_no_setup_connection() { | void APIConnection::on_no_setup_connection() { | ||||||
|   this->on_fatal_error(); |   this->on_fatal_error(); | ||||||
|   ESP_LOGD(TAG, "%s requested access without full connection", this->client_combined_info_.c_str()); |   ESP_LOGD(TAG, "%s requested access without full connection", this->get_client_combined_info().c_str()); | ||||||
| } | } | ||||||
| void APIConnection::on_fatal_error() { | void APIConnection::on_fatal_error() { | ||||||
|   this->helper_->close(); |   this->helper_->close(); | ||||||
| @@ -1791,7 +1806,7 @@ void APIConnection::process_batch_() { | |||||||
|   this->batch_first_message_ = true; |   this->batch_first_message_ = true; | ||||||
|  |  | ||||||
|   size_t items_processed = 0; |   size_t items_processed = 0; | ||||||
|   uint32_t remaining_size = MAX_PACKET_SIZE; |   uint16_t remaining_size = std::numeric_limits<uint16_t>::max(); | ||||||
|  |  | ||||||
|   // Track where each message's header padding begins in the buffer |   // Track where each message's header padding begins in the buffer | ||||||
|   // For plaintext: this is where the 6-byte header padding starts |   // For plaintext: this is where the 6-byte header padding starts | ||||||
| @@ -1816,11 +1831,15 @@ void APIConnection::process_batch_() { | |||||||
|     packet_info.emplace_back(item.message_type, current_offset, proto_payload_size); |     packet_info.emplace_back(item.message_type, current_offset, proto_payload_size); | ||||||
|  |  | ||||||
|     // Update tracking variables |     // Update tracking variables | ||||||
|  |     items_processed++; | ||||||
|  |     // After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation | ||||||
|  |     if (items_processed == 1) { | ||||||
|  |       remaining_size = MAX_PACKET_SIZE; | ||||||
|  |     } | ||||||
|     remaining_size -= payload_size; |     remaining_size -= payload_size; | ||||||
|     // Calculate where the next message's header padding will start |     // Calculate where the next message's header padding will start | ||||||
|     // Current buffer size + footer space (that prepare_message_buffer will add for this message) |     // Current buffer size + footer space (that prepare_message_buffer will add for this message) | ||||||
|     current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size; |     current_offset = this->parent_->get_shared_buffer_ref().size() + footer_size; | ||||||
|     items_processed++; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (items_processed == 0) { |   if (items_processed == 0) { | ||||||
| @@ -1840,10 +1859,10 @@ void APIConnection::process_batch_() { | |||||||
|   if (err != APIError::OK && err != APIError::WOULD_BLOCK) { |   if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||||
|     on_fatal_error(); |     on_fatal_error(); | ||||||
|     if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { |     if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { | ||||||
|       ESP_LOGW(TAG, "%s: Connection reset during batch write", this->client_combined_info_.c_str()); |       ESP_LOGW(TAG, "%s: Connection reset during batch write", this->get_client_combined_info().c_str()); | ||||||
|     } else { |     } else { | ||||||
|       ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), |       ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), | ||||||
|                errno); |                api_error_to_str(err), errno); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -240,8 +240,8 @@ class APIConnection : public APIServerConnection { | |||||||
|     // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) |     // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext) | ||||||
|     // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext) |     // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext) | ||||||
|     shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size()); |     shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size()); | ||||||
|     // Insert header padding bytes so message encoding starts at the correct position |     // Resize to add header padding so message encoding starts at the correct position | ||||||
|     shared_buf.insert(shared_buf.begin(), header_padding, 0); |     shared_buf.resize(header_padding); | ||||||
|     return {&shared_buf}; |     return {&shared_buf}; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -249,47 +249,47 @@ class APIConnection : public APIServerConnection { | |||||||
|   ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) { |   ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) { | ||||||
|     // Get reference to shared buffer (it maintains state between batch messages) |     // Get reference to shared buffer (it maintains state between batch messages) | ||||||
|     std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref(); |     std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref(); | ||||||
|     size_t current_size = shared_buf.size(); |  | ||||||
|  |  | ||||||
|     if (is_first_message) { |     if (is_first_message) { | ||||||
|       // For first message, initialize buffer with header padding |  | ||||||
|       uint8_t header_padding = this->helper_->frame_header_padding(); |  | ||||||
|       shared_buf.clear(); |       shared_buf.clear(); | ||||||
|       shared_buf.reserve(message_size + header_padding); |  | ||||||
|       shared_buf.resize(header_padding); |  | ||||||
|       // Fill header padding with zeros |  | ||||||
|       std::fill(shared_buf.begin(), shared_buf.end(), 0); |  | ||||||
|     } else { |  | ||||||
|       // For subsequent messages, add footer space for previous message and header for this message |  | ||||||
|       uint8_t footer_size = this->helper_->frame_footer_size(); |  | ||||||
|       uint8_t header_padding = this->helper_->frame_header_padding(); |  | ||||||
|  |  | ||||||
|       // Reserve additional space for everything |  | ||||||
|       shared_buf.reserve(current_size + footer_size + header_padding + message_size); |  | ||||||
|  |  | ||||||
|       // Single resize to add both footer and header padding |  | ||||||
|       size_t new_size = current_size + footer_size + header_padding; |  | ||||||
|       shared_buf.resize(new_size); |  | ||||||
|  |  | ||||||
|       // Fill the newly added bytes with zeros (footer + header padding) |  | ||||||
|       std::fill(shared_buf.begin() + current_size, shared_buf.end(), 0); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     size_t current_size = shared_buf.size(); | ||||||
|  |  | ||||||
|  |     // Calculate padding to add: | ||||||
|  |     // - First message: just header padding | ||||||
|  |     // - Subsequent messages: footer for previous message + header padding for this message | ||||||
|  |     size_t padding_to_add = is_first_message | ||||||
|  |                                 ? this->helper_->frame_header_padding() | ||||||
|  |                                 : this->helper_->frame_header_padding() + this->helper_->frame_footer_size(); | ||||||
|  |  | ||||||
|  |     // Reserve space for padding + message | ||||||
|  |     shared_buf.reserve(current_size + padding_to_add + message_size); | ||||||
|  |  | ||||||
|  |     // Resize to add the padding bytes | ||||||
|  |     shared_buf.resize(current_size + padding_to_add); | ||||||
|  |  | ||||||
|     return {&shared_buf}; |     return {&shared_buf}; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool try_to_clear_buffer(bool log_out_of_space); |   bool try_to_clear_buffer(bool log_out_of_space); | ||||||
|   bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override; |   bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override; | ||||||
|  |  | ||||||
|   std::string get_client_combined_info() const { return this->client_combined_info_; } |   std::string get_client_combined_info() const { | ||||||
|  |     if (this->client_info_ == this->client_peername_) { | ||||||
|  |       // Before Hello message, both are the same (just IP:port) | ||||||
|  |       return this->client_info_; | ||||||
|  |     } | ||||||
|  |     return this->client_info_ + " (" + this->client_peername_ + ")"; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Buffer allocator methods for batch processing |   // Buffer allocator methods for batch processing | ||||||
|   ProtoWriteBuffer allocate_single_message_buffer(uint16_t size); |   ProtoWriteBuffer allocate_single_message_buffer(uint16_t size); | ||||||
|   ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size); |   ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   // Helper function to fill common entity fields |   // Helper function to fill common entity info fields | ||||||
|   template<typename ResponseT> static void fill_entity_info_base(esphome::EntityBase *entity, ResponseT &response) { |   static void fill_entity_info_base(esphome::EntityBase *entity, InfoResponseProtoMessage &response) { | ||||||
|     // Set common fields that are shared by all entity types |     // Set common fields that are shared by all entity types | ||||||
|     response.key = entity->get_object_id_hash(); |     response.key = entity->get_object_id_hash(); | ||||||
|     response.object_id = entity->get_object_id(); |     response.object_id = entity->get_object_id(); | ||||||
| @@ -303,6 +303,11 @@ class APIConnection : public APIServerConnection { | |||||||
|     response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); |     response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Helper function to fill common entity state fields | ||||||
|  |   static void fill_entity_state_base(esphome::EntityBase *entity, StateResponseProtoMessage &response) { | ||||||
|  |     response.key = entity->get_object_id_hash(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Non-template helper to encode any ProtoMessage |   // Non-template helper to encode any ProtoMessage | ||||||
|   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, |   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, | ||||||
|                                            uint32_t remaining_size, bool is_single); |                                            uint32_t remaining_size, bool is_single); | ||||||
| @@ -433,37 +438,44 @@ class APIConnection : public APIServerConnection { | |||||||
|   // Helper function to get estimated message size for buffer pre-allocation |   // Helper function to get estimated message size for buffer pre-allocation | ||||||
|   static uint16_t get_estimated_message_size(uint16_t message_type); |   static uint16_t get_estimated_message_size(uint16_t message_type); | ||||||
|  |  | ||||||
|   enum class ConnectionState { |   // Pointers first (4 bytes each, naturally aligned) | ||||||
|  |   std::unique_ptr<APIFrameHelper> helper_; | ||||||
|  |   APIServer *parent_; | ||||||
|  |  | ||||||
|  |   // 4-byte aligned types | ||||||
|  |   uint32_t last_traffic_; | ||||||
|  |   uint32_t next_ping_retry_{0}; | ||||||
|  |   int state_subs_at_ = -1; | ||||||
|  |  | ||||||
|  |   // Strings (12 bytes each on 32-bit) | ||||||
|  |   std::string client_info_; | ||||||
|  |   std::string client_peername_; | ||||||
|  |  | ||||||
|  |   // 2-byte aligned types | ||||||
|  |   uint16_t client_api_version_major_{0}; | ||||||
|  |   uint16_t client_api_version_minor_{0}; | ||||||
|  |  | ||||||
|  |   // Group all 1-byte types together to minimize padding | ||||||
|  |   enum class ConnectionState : uint8_t { | ||||||
|     WAITING_FOR_HELLO, |     WAITING_FOR_HELLO, | ||||||
|     CONNECTED, |     CONNECTED, | ||||||
|     AUTHENTICATED, |     AUTHENTICATED, | ||||||
|   } connection_state_{ConnectionState::WAITING_FOR_HELLO}; |   } connection_state_{ConnectionState::WAITING_FOR_HELLO}; | ||||||
|  |   uint8_t log_subscription_{ESPHOME_LOG_LEVEL_NONE}; | ||||||
|   bool remove_{false}; |   bool remove_{false}; | ||||||
|  |  | ||||||
|   std::unique_ptr<APIFrameHelper> helper_; |  | ||||||
|  |  | ||||||
|   std::string client_info_; |  | ||||||
|   std::string client_peername_; |  | ||||||
|   std::string client_combined_info_; |  | ||||||
|   uint32_t client_api_version_major_{0}; |  | ||||||
|   uint32_t client_api_version_minor_{0}; |  | ||||||
| #ifdef USE_ESP32_CAMERA |  | ||||||
|   esp32_camera::CameraImageReader image_reader_; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   bool state_subscription_{false}; |   bool state_subscription_{false}; | ||||||
|   int log_subscription_{ESPHOME_LOG_LEVEL_NONE}; |  | ||||||
|   uint32_t last_traffic_; |  | ||||||
|   uint32_t next_ping_retry_{0}; |  | ||||||
|   uint8_t ping_retries_{0}; |  | ||||||
|   bool sent_ping_{false}; |   bool sent_ping_{false}; | ||||||
|   bool service_call_subscription_{false}; |   bool service_call_subscription_{false}; | ||||||
|   bool next_close_ = false; |   bool next_close_ = false; | ||||||
|   APIServer *parent_; |   uint8_t ping_retries_{0}; | ||||||
|  |   // 8 bytes used, no padding needed | ||||||
|  |  | ||||||
|  |   // Larger objects at the end | ||||||
|   InitialStateIterator initial_state_iterator_; |   InitialStateIterator initial_state_iterator_; | ||||||
|   ListEntitiesIterator list_entities_iterator_; |   ListEntitiesIterator list_entities_iterator_; | ||||||
|   int state_subs_at_ = -1; | #ifdef USE_ESP32_CAMERA | ||||||
|  |   esp32_camera::CameraImageReader image_reader_; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   // 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); | ||||||
|   | |||||||
| @@ -125,38 +125,6 @@ class APIFrameHelper { | |||||||
|     const uint8_t *current_data() const { return data.data() + offset; } |     const uint8_t *current_data() const { return data.data() + offset; } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // Queue of data buffers to be sent |  | ||||||
|   std::deque<SendBuffer> tx_buf_; |  | ||||||
|  |  | ||||||
|   // Common state enum for all frame helpers |  | ||||||
|   // Note: Not all states are used by all implementations |  | ||||||
|   // - INITIALIZE: Used by both Noise and Plaintext |  | ||||||
|   // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol |  | ||||||
|   // - DATA: Used by both Noise and Plaintext |  | ||||||
|   // - CLOSED: Used by both Noise and Plaintext |  | ||||||
|   // - FAILED: Used by both Noise and Plaintext |  | ||||||
|   // - EXPLICIT_REJECT: Only used by Noise protocol |  | ||||||
|   enum class State { |  | ||||||
|     INITIALIZE = 1, |  | ||||||
|     CLIENT_HELLO = 2,  // Noise only |  | ||||||
|     SERVER_HELLO = 3,  // Noise only |  | ||||||
|     HANDSHAKE = 4,     // Noise only |  | ||||||
|     DATA = 5, |  | ||||||
|     CLOSED = 6, |  | ||||||
|     FAILED = 7, |  | ||||||
|     EXPLICIT_REJECT = 8,  // Noise only |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   // Current state of the frame helper |  | ||||||
|   State state_{State::INITIALIZE}; |  | ||||||
|  |  | ||||||
|   // Helper name for logging |  | ||||||
|   std::string info_; |  | ||||||
|  |  | ||||||
|   // Socket for communication |  | ||||||
|   socket::Socket *socket_{nullptr}; |  | ||||||
|   std::unique_ptr<socket::Socket> socket_owned_; |  | ||||||
|  |  | ||||||
|   // Common implementation for writing raw data to socket |   // Common implementation for writing raw data to socket | ||||||
|   APIError write_raw_(const struct iovec *iov, int iovcnt); |   APIError write_raw_(const struct iovec *iov, int iovcnt); | ||||||
|  |  | ||||||
| @@ -169,15 +137,41 @@ class APIFrameHelper { | |||||||
|   APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf, |   APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf, | ||||||
|                       const std::string &info, StateEnum &state, StateEnum failed_state); |                       const std::string &info, StateEnum &state, StateEnum failed_state); | ||||||
|  |  | ||||||
|  |   // Pointers first (4 bytes each) | ||||||
|  |   socket::Socket *socket_{nullptr}; | ||||||
|  |   std::unique_ptr<socket::Socket> socket_owned_; | ||||||
|  |  | ||||||
|  |   // Common state enum for all frame helpers | ||||||
|  |   // Note: Not all states are used by all implementations | ||||||
|  |   // - INITIALIZE: Used by both Noise and Plaintext | ||||||
|  |   // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol | ||||||
|  |   // - DATA: Used by both Noise and Plaintext | ||||||
|  |   // - CLOSED: Used by both Noise and Plaintext | ||||||
|  |   // - FAILED: Used by both Noise and Plaintext | ||||||
|  |   // - EXPLICIT_REJECT: Only used by Noise protocol | ||||||
|  |   enum class State : uint8_t { | ||||||
|  |     INITIALIZE = 1, | ||||||
|  |     CLIENT_HELLO = 2,  // Noise only | ||||||
|  |     SERVER_HELLO = 3,  // Noise only | ||||||
|  |     HANDSHAKE = 4,     // Noise only | ||||||
|  |     DATA = 5, | ||||||
|  |     CLOSED = 6, | ||||||
|  |     FAILED = 7, | ||||||
|  |     EXPLICIT_REJECT = 8,  // Noise only | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // Containers (size varies, but typically 12+ bytes on 32-bit) | ||||||
|  |   std::deque<SendBuffer> tx_buf_; | ||||||
|  |   std::string info_; | ||||||
|  |   std::vector<struct iovec> reusable_iovs_; | ||||||
|  |   std::vector<uint8_t> rx_buf_; | ||||||
|  |  | ||||||
|  |   // Group smaller types together | ||||||
|  |   uint16_t rx_buf_len_ = 0; | ||||||
|  |   State state_{State::INITIALIZE}; | ||||||
|   uint8_t frame_header_padding_{0}; |   uint8_t frame_header_padding_{0}; | ||||||
|   uint8_t frame_footer_size_{0}; |   uint8_t frame_footer_size_{0}; | ||||||
|  |   // 5 bytes total, 3 bytes padding | ||||||
|   // Reusable IOV array for write_protobuf_packets to avoid repeated allocations |  | ||||||
|   std::vector<struct iovec> reusable_iovs_; |  | ||||||
|  |  | ||||||
|   // Receive buffer for reading frame data |  | ||||||
|   std::vector<uint8_t> rx_buf_; |  | ||||||
|   uint16_t rx_buf_len_ = 0; |  | ||||||
|  |  | ||||||
|   // Common initialization for both plaintext and noise protocols |   // Common initialization for both plaintext and noise protocols | ||||||
|   APIError init_common_(); |   APIError init_common_(); | ||||||
| @@ -213,19 +207,28 @@ class APINoiseFrameHelper : public APIFrameHelper { | |||||||
|   APIError init_handshake_(); |   APIError init_handshake_(); | ||||||
|   APIError check_handshake_finished_(); |   APIError check_handshake_finished_(); | ||||||
|   void send_explicit_handshake_reject_(const std::string &reason); |   void send_explicit_handshake_reject_(const std::string &reason); | ||||||
|  |  | ||||||
|  |   // Pointers first (4 bytes each) | ||||||
|  |   NoiseHandshakeState *handshake_{nullptr}; | ||||||
|  |   NoiseCipherState *send_cipher_{nullptr}; | ||||||
|  |   NoiseCipherState *recv_cipher_{nullptr}; | ||||||
|  |  | ||||||
|  |   // Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer) | ||||||
|  |   std::shared_ptr<APINoiseContext> ctx_; | ||||||
|  |  | ||||||
|  |   // Vector (12 bytes on 32-bit) | ||||||
|  |   std::vector<uint8_t> prologue_; | ||||||
|  |  | ||||||
|  |   // NoiseProtocolId (size depends on implementation) | ||||||
|  |   NoiseProtocolId nid_; | ||||||
|  |  | ||||||
|  |   // Group small types together | ||||||
|   // Fixed-size header buffer for noise protocol: |   // Fixed-size header buffer for noise protocol: | ||||||
|   // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint) |   // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint) | ||||||
|   // Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase |   // Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase | ||||||
|   uint8_t rx_header_buf_[3]; |   uint8_t rx_header_buf_[3]; | ||||||
|   uint8_t rx_header_buf_len_ = 0; |   uint8_t rx_header_buf_len_ = 0; | ||||||
|  |   // 4 bytes total, no padding | ||||||
|   std::vector<uint8_t> prologue_; |  | ||||||
|  |  | ||||||
|   std::shared_ptr<APINoiseContext> ctx_; |  | ||||||
|   NoiseHandshakeState *handshake_{nullptr}; |  | ||||||
|   NoiseCipherState *send_cipher_{nullptr}; |  | ||||||
|   NoiseCipherState *recv_cipher_{nullptr}; |  | ||||||
|   NoiseProtocolId nid_; |  | ||||||
| }; | }; | ||||||
| #endif  // USE_API_NOISE | #endif  // USE_API_NOISE | ||||||
|  |  | ||||||
| @@ -252,6 +255,12 @@ class APIPlaintextFrameHelper : public APIFrameHelper { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   APIError try_read_frame_(ParsedFrame *frame); |   APIError try_read_frame_(ParsedFrame *frame); | ||||||
|  |  | ||||||
|  |   // Group 2-byte aligned types | ||||||
|  |   uint16_t rx_header_parsed_type_ = 0; | ||||||
|  |   uint16_t rx_header_parsed_len_ = 0; | ||||||
|  |  | ||||||
|  |   // Group 1-byte types together | ||||||
|   // Fixed-size header buffer for plaintext protocol: |   // Fixed-size header buffer for plaintext protocol: | ||||||
|   // We now store the indicator byte + the two varints. |   // We now store the indicator byte + the two varints. | ||||||
|   // To match noise protocol's maximum message size (UINT16_MAX = 65535), we need: |   // To match noise protocol's maximum message size (UINT16_MAX = 65535), we need: | ||||||
| @@ -263,8 +272,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper { | |||||||
|   uint8_t rx_header_buf_[6];  // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type) |   uint8_t rx_header_buf_[6];  // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type) | ||||||
|   uint8_t rx_header_buf_pos_ = 0; |   uint8_t rx_header_buf_pos_ = 0; | ||||||
|   bool rx_header_parsed_ = false; |   bool rx_header_parsed_ = false; | ||||||
|   uint16_t rx_header_parsed_type_ = 0; |   // 8 bytes total, no padding needed | ||||||
|   uint16_t rx_header_parsed_len_ = 0; |  | ||||||
| }; | }; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,4 +21,5 @@ extend google.protobuf.MessageOptions { | |||||||
|     optional string ifdef = 1038; |     optional string ifdef = 1038; | ||||||
|     optional bool log = 1039 [default=true]; |     optional bool log = 1039 [default=true]; | ||||||
|     optional bool no_delay = 1040 [default=false]; |     optional bool no_delay = 1040 [default=false]; | ||||||
|  |     optional string base_class = 1041; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -516,6 +516,8 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V | |||||||
|       return "VOICE_ASSISTANT_TTS_STREAM_START"; |       return "VOICE_ASSISTANT_TTS_STREAM_START"; | ||||||
|     case enums::VOICE_ASSISTANT_TTS_STREAM_END: |     case enums::VOICE_ASSISTANT_TTS_STREAM_END: | ||||||
|       return "VOICE_ASSISTANT_TTS_STREAM_END"; |       return "VOICE_ASSISTANT_TTS_STREAM_END"; | ||||||
|  |     case enums::VOICE_ASSISTANT_INTENT_PROGRESS: | ||||||
|  |       return "VOICE_ASSISTANT_INTENT_PROGRESS"; | ||||||
|     default: |     default: | ||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| @@ -628,6 +630,7 @@ template<> const char *proto_enum_to_string<enums::UpdateCommand>(enums::UpdateC | |||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 2: { |     case 2: { | ||||||
| @@ -794,28 +797,18 @@ void ConnectResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void DisconnectRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void DisconnectRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } | void DisconnectRequest::dump_to(std::string &out) const { out.append("DisconnectRequest {}"); } | ||||||
| #endif | #endif | ||||||
| void DisconnectResponse::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void DisconnectResponse::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } | void DisconnectResponse::dump_to(std::string &out) const { out.append("DisconnectResponse {}"); } | ||||||
| #endif | #endif | ||||||
| void PingRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void PingRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } | void PingRequest::dump_to(std::string &out) const { out.append("PingRequest {}"); } | ||||||
| #endif | #endif | ||||||
| void PingResponse::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void PingResponse::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); } | void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}"); } | ||||||
| #endif | #endif | ||||||
| void DeviceInfoRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void DeviceInfoRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #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 | ||||||
| @@ -1036,18 +1029,12 @@ void DeviceInfoResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void ListEntitiesRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void ListEntitiesRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } | void ListEntitiesRequest::dump_to(std::string &out) const { out.append("ListEntitiesRequest {}"); } | ||||||
| #endif | #endif | ||||||
| void ListEntitiesDoneResponse::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void ListEntitiesDoneResponse::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } | void ListEntitiesDoneResponse::dump_to(std::string &out) const { out.append("ListEntitiesDoneResponse {}"); } | ||||||
| #endif | #endif | ||||||
| void SubscribeStatesRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void SubscribeStatesRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); } | void SubscribeStatesRequest::dump_to(std::string &out) const { out.append("SubscribeStatesRequest {}"); } | ||||||
| #endif | #endif | ||||||
| @@ -3368,8 +3355,6 @@ void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void SubscribeHomeassistantServicesRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void SubscribeHomeassistantServicesRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { | void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { | ||||||
|   out.append("SubscribeHomeassistantServicesRequest {}"); |   out.append("SubscribeHomeassistantServicesRequest {}"); | ||||||
| @@ -3495,8 +3480,6 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void SubscribeHomeAssistantStatesRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void SubscribeHomeAssistantStatesRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { | void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { | ||||||
|   out.append("SubscribeHomeAssistantStatesRequest {}"); |   out.append("SubscribeHomeAssistantStatesRequest {}"); | ||||||
| @@ -3600,8 +3583,6 @@ void HomeAssistantStateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void GetTimeRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void GetTimeRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } | void GetTimeRequest::dump_to(std::string &out) const { out.append("GetTimeRequest {}"); } | ||||||
| #endif | #endif | ||||||
| @@ -7496,8 +7477,6 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void SubscribeBluetoothConnectionsFreeRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void SubscribeBluetoothConnectionsFreeRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const { | void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const { | ||||||
|   out.append("SubscribeBluetoothConnectionsFreeRequest {}"); |   out.append("SubscribeBluetoothConnectionsFreeRequest {}"); | ||||||
| @@ -7781,8 +7760,6 @@ void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void UnsubscribeBluetoothLEAdvertisementsRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void UnsubscribeBluetoothLEAdvertisementsRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { | void UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(std::string &out) const { | ||||||
|   out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}"); |   out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}"); | ||||||
| @@ -8448,8 +8425,6 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {} |  | ||||||
| void VoiceAssistantConfigurationRequest::calculate_size(uint32_t &total_size) const {} |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { | void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { | ||||||
|   out.append("VoiceAssistantConfigurationRequest {}"); |   out.append("VoiceAssistantConfigurationRequest {}"); | ||||||
|   | |||||||
| @@ -208,6 +208,7 @@ enum VoiceAssistantEvent : uint32_t { | |||||||
|   VOICE_ASSISTANT_STT_VAD_END = 12, |   VOICE_ASSISTANT_STT_VAD_END = 12, | ||||||
|   VOICE_ASSISTANT_TTS_STREAM_START = 98, |   VOICE_ASSISTANT_TTS_STREAM_START = 98, | ||||||
|   VOICE_ASSISTANT_TTS_STREAM_END = 99, |   VOICE_ASSISTANT_TTS_STREAM_END = 99, | ||||||
|  |   VOICE_ASSISTANT_INTENT_PROGRESS = 100, | ||||||
| }; | }; | ||||||
| enum VoiceAssistantTimerEvent : uint32_t { | enum VoiceAssistantTimerEvent : uint32_t { | ||||||
|   VOICE_ASSISTANT_TIMER_STARTED = 0, |   VOICE_ASSISTANT_TIMER_STARTED = 0, | ||||||
| @@ -253,6 +254,27 @@ enum UpdateCommand : uint32_t { | |||||||
|  |  | ||||||
| }  // namespace enums | }  // namespace enums | ||||||
|  |  | ||||||
|  | class InfoResponseProtoMessage : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   ~InfoResponseProtoMessage() override = default; | ||||||
|  |   std::string object_id{}; | ||||||
|  |   uint32_t key{0}; | ||||||
|  |   std::string name{}; | ||||||
|  |   std::string unique_id{}; | ||||||
|  |   bool disabled_by_default{false}; | ||||||
|  |   std::string icon{}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class StateResponseProtoMessage : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   ~StateResponseProtoMessage() override = default; | ||||||
|  |   uint32_t key{0}; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  | }; | ||||||
| class HelloRequest : public ProtoMessage { | class HelloRequest : public ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 1; |   static constexpr uint16_t MESSAGE_TYPE = 1; | ||||||
| @@ -335,8 +357,6 @@ class DisconnectRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "disconnect_request"; } |   static constexpr const char *message_name() { return "disconnect_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -350,8 +370,6 @@ class DisconnectResponse : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "disconnect_response"; } |   static constexpr const char *message_name() { return "disconnect_response"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -365,8 +383,6 @@ class PingRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "ping_request"; } |   static constexpr const char *message_name() { return "ping_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -380,8 +396,6 @@ class PingResponse : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "ping_response"; } |   static constexpr const char *message_name() { return "ping_response"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -395,8 +409,6 @@ class DeviceInfoRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "device_info_request"; } |   static constexpr const char *message_name() { return "device_info_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -446,8 +458,6 @@ class ListEntitiesRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_request"; } |   static constexpr const char *message_name() { return "list_entities_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -461,8 +471,6 @@ class ListEntitiesDoneResponse : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_done_response"; } |   static constexpr const char *message_name() { return "list_entities_done_response"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -476,30 +484,21 @@ class SubscribeStatesRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "subscribe_states_request"; } |   static constexpr const char *message_name() { return "subscribe_states_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| }; | }; | ||||||
| class ListEntitiesBinarySensorResponse : public ProtoMessage { | class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 12; |   static constexpr uint16_t MESSAGE_TYPE = 12; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 56; |   static constexpr uint16_t ESTIMATED_SIZE = 56; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; } |   static constexpr const char *message_name() { return "list_entities_binary_sensor_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   bool is_status_binary_sensor{false}; |   bool is_status_binary_sensor{false}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -511,14 +510,13 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class BinarySensorStateResponse : public ProtoMessage { | class BinarySensorStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 21; |   static constexpr uint16_t MESSAGE_TYPE = 21; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 9; |   static constexpr uint16_t ESTIMATED_SIZE = 9; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "binary_sensor_state_response"; } |   static constexpr const char *message_name() { return "binary_sensor_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool state{false}; |   bool state{false}; | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -531,24 +529,17 @@ class BinarySensorStateResponse : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesCoverResponse : public ProtoMessage { | class ListEntitiesCoverResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 13; |   static constexpr uint16_t MESSAGE_TYPE = 13; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 62; |   static constexpr uint16_t ESTIMATED_SIZE = 62; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_cover_response"; } |   static constexpr const char *message_name() { return "list_entities_cover_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   bool assumed_state{false}; |   bool assumed_state{false}; | ||||||
|   bool supports_position{false}; |   bool supports_position{false}; | ||||||
|   bool supports_tilt{false}; |   bool supports_tilt{false}; | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   bool supports_stop{false}; |   bool supports_stop{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -561,14 +552,13 @@ class ListEntitiesCoverResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class CoverStateResponse : public ProtoMessage { | class CoverStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 22; |   static constexpr uint16_t MESSAGE_TYPE = 22; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 19; |   static constexpr uint16_t ESTIMATED_SIZE = 19; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "cover_state_response"; } |   static constexpr const char *message_name() { return "cover_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   enums::LegacyCoverState legacy_state{}; |   enums::LegacyCoverState legacy_state{}; | ||||||
|   float position{0.0f}; |   float position{0.0f}; | ||||||
|   float tilt{0.0f}; |   float tilt{0.0f}; | ||||||
| @@ -608,24 +598,17 @@ class CoverCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesFanResponse : public ProtoMessage { | class ListEntitiesFanResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 14; |   static constexpr uint16_t MESSAGE_TYPE = 14; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 73; |   static constexpr uint16_t ESTIMATED_SIZE = 73; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_fan_response"; } |   static constexpr const char *message_name() { return "list_entities_fan_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   bool supports_oscillation{false}; |   bool supports_oscillation{false}; | ||||||
|   bool supports_speed{false}; |   bool supports_speed{false}; | ||||||
|   bool supports_direction{false}; |   bool supports_direction{false}; | ||||||
|   int32_t supported_speed_count{0}; |   int32_t supported_speed_count{0}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::vector<std::string> supported_preset_modes{}; |   std::vector<std::string> supported_preset_modes{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -638,14 +621,13 @@ class ListEntitiesFanResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class FanStateResponse : public ProtoMessage { | class FanStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 23; |   static constexpr uint16_t MESSAGE_TYPE = 23; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 26; |   static constexpr uint16_t ESTIMATED_SIZE = 26; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "fan_state_response"; } |   static constexpr const char *message_name() { return "fan_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool state{false}; |   bool state{false}; | ||||||
|   bool oscillating{false}; |   bool oscillating{false}; | ||||||
|   enums::FanSpeed speed{}; |   enums::FanSpeed speed{}; | ||||||
| @@ -694,17 +676,13 @@ class FanCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesLightResponse : public ProtoMessage { | class ListEntitiesLightResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 15; |   static constexpr uint16_t MESSAGE_TYPE = 15; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 85; |   static constexpr uint16_t ESTIMATED_SIZE = 85; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_light_response"; } |   static constexpr const char *message_name() { return "list_entities_light_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::vector<enums::ColorMode> supported_color_modes{}; |   std::vector<enums::ColorMode> supported_color_modes{}; | ||||||
|   bool legacy_supports_brightness{false}; |   bool legacy_supports_brightness{false}; | ||||||
|   bool legacy_supports_rgb{false}; |   bool legacy_supports_rgb{false}; | ||||||
| @@ -713,9 +691,6 @@ class ListEntitiesLightResponse : public ProtoMessage { | |||||||
|   float min_mireds{0.0f}; |   float min_mireds{0.0f}; | ||||||
|   float max_mireds{0.0f}; |   float max_mireds{0.0f}; | ||||||
|   std::vector<std::string> effects{}; |   std::vector<std::string> effects{}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -727,14 +702,13 @@ class ListEntitiesLightResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class LightStateResponse : public ProtoMessage { | class LightStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 24; |   static constexpr uint16_t MESSAGE_TYPE = 24; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 63; |   static constexpr uint16_t ESTIMATED_SIZE = 63; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "light_state_response"; } |   static constexpr const char *message_name() { return "light_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool state{false}; |   bool state{false}; | ||||||
|   float brightness{0.0f}; |   float brightness{0.0f}; | ||||||
|   enums::ColorMode color_mode{}; |   enums::ColorMode color_mode{}; | ||||||
| @@ -803,26 +777,19 @@ class LightCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesSensorResponse : public ProtoMessage { | class ListEntitiesSensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 16; |   static constexpr uint16_t MESSAGE_TYPE = 16; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 73; |   static constexpr uint16_t ESTIMATED_SIZE = 73; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_sensor_response"; } |   static constexpr const char *message_name() { return "list_entities_sensor_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   std::string unit_of_measurement{}; |   std::string unit_of_measurement{}; | ||||||
|   int32_t accuracy_decimals{0}; |   int32_t accuracy_decimals{0}; | ||||||
|   bool force_update{false}; |   bool force_update{false}; | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   enums::SensorStateClass state_class{}; |   enums::SensorStateClass state_class{}; | ||||||
|   enums::SensorLastResetType legacy_last_reset_type{}; |   enums::SensorLastResetType legacy_last_reset_type{}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -834,14 +801,13 @@ class ListEntitiesSensorResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class SensorStateResponse : public ProtoMessage { | class SensorStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 25; |   static constexpr uint16_t MESSAGE_TYPE = 25; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 12; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "sensor_state_response"; } |   static constexpr const char *message_name() { return "sensor_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   float state{0.0f}; |   float state{0.0f}; | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -854,21 +820,14 @@ class SensorStateResponse : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesSwitchResponse : public ProtoMessage { | class ListEntitiesSwitchResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 17; |   static constexpr uint16_t MESSAGE_TYPE = 17; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 56; |   static constexpr uint16_t ESTIMATED_SIZE = 56; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_switch_response"; } |   static constexpr const char *message_name() { return "list_entities_switch_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool assumed_state{false}; |   bool assumed_state{false}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -881,14 +840,13 @@ class ListEntitiesSwitchResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class SwitchStateResponse : public ProtoMessage { | class SwitchStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 26; |   static constexpr uint16_t MESSAGE_TYPE = 26; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 7; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "switch_state_response"; } |   static constexpr const char *message_name() { return "switch_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool state{false}; |   bool state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -919,20 +877,13 @@ class SwitchCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesTextSensorResponse : public ProtoMessage { | class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 18; |   static constexpr uint16_t MESSAGE_TYPE = 18; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 54; |   static constexpr uint16_t ESTIMATED_SIZE = 54; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_text_sensor_response"; } |   static constexpr const char *message_name() { return "list_entities_text_sensor_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -945,14 +896,13 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class TextSensorStateResponse : public ProtoMessage { | class TextSensorStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 27; |   static constexpr uint16_t MESSAGE_TYPE = 27; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 16; |   static constexpr uint16_t ESTIMATED_SIZE = 16; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "text_sensor_state_response"; } |   static constexpr const char *message_name() { return "text_sensor_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string state{}; |   std::string state{}; | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -1045,8 +995,6 @@ class SubscribeHomeassistantServicesRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "subscribe_homeassistant_services_request"; } |   static constexpr const char *message_name() { return "subscribe_homeassistant_services_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -1095,8 +1043,6 @@ class SubscribeHomeAssistantStatesRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "subscribe_home_assistant_states_request"; } |   static constexpr const char *message_name() { return "subscribe_home_assistant_states_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -1149,8 +1095,6 @@ class GetTimeRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "get_time_request"; } |   static constexpr const char *message_name() { return "get_time_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -1249,20 +1193,13 @@ class ExecuteServiceRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesCameraResponse : public ProtoMessage { | class ListEntitiesCameraResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 43; |   static constexpr uint16_t MESSAGE_TYPE = 43; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 45; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_camera_response"; } |   static constexpr const char *message_name() { return "list_entities_camera_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -1313,17 +1250,13 @@ class CameraImageRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesClimateResponse : public ProtoMessage { | class ListEntitiesClimateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 46; |   static constexpr uint16_t MESSAGE_TYPE = 46; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 151; |   static constexpr uint16_t ESTIMATED_SIZE = 151; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_climate_response"; } |   static constexpr const char *message_name() { return "list_entities_climate_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   bool supports_current_temperature{false}; |   bool supports_current_temperature{false}; | ||||||
|   bool supports_two_point_target_temperature{false}; |   bool supports_two_point_target_temperature{false}; | ||||||
|   std::vector<enums::ClimateMode> supported_modes{}; |   std::vector<enums::ClimateMode> supported_modes{}; | ||||||
| @@ -1337,9 +1270,6 @@ class ListEntitiesClimateResponse : public ProtoMessage { | |||||||
|   std::vector<std::string> supported_custom_fan_modes{}; |   std::vector<std::string> supported_custom_fan_modes{}; | ||||||
|   std::vector<enums::ClimatePreset> supported_presets{}; |   std::vector<enums::ClimatePreset> supported_presets{}; | ||||||
|   std::vector<std::string> supported_custom_presets{}; |   std::vector<std::string> supported_custom_presets{}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   float visual_current_temperature_step{0.0f}; |   float visual_current_temperature_step{0.0f}; | ||||||
|   bool supports_current_humidity{false}; |   bool supports_current_humidity{false}; | ||||||
|   bool supports_target_humidity{false}; |   bool supports_target_humidity{false}; | ||||||
| @@ -1356,14 +1286,13 @@ class ListEntitiesClimateResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ClimateStateResponse : public ProtoMessage { | class ClimateStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 47; |   static constexpr uint16_t MESSAGE_TYPE = 47; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 65; |   static constexpr uint16_t ESTIMATED_SIZE = 65; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "climate_state_response"; } |   static constexpr const char *message_name() { return "climate_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   enums::ClimateMode mode{}; |   enums::ClimateMode mode{}; | ||||||
|   float current_temperature{0.0f}; |   float current_temperature{0.0f}; | ||||||
|   float target_temperature{0.0f}; |   float target_temperature{0.0f}; | ||||||
| @@ -1430,23 +1359,16 @@ class ClimateCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesNumberResponse : public ProtoMessage { | class ListEntitiesNumberResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 49; |   static constexpr uint16_t MESSAGE_TYPE = 49; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 80; |   static constexpr uint16_t ESTIMATED_SIZE = 80; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_number_response"; } |   static constexpr const char *message_name() { return "list_entities_number_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   float min_value{0.0f}; |   float min_value{0.0f}; | ||||||
|   float max_value{0.0f}; |   float max_value{0.0f}; | ||||||
|   float step{0.0f}; |   float step{0.0f}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::string unit_of_measurement{}; |   std::string unit_of_measurement{}; | ||||||
|   enums::NumberMode mode{}; |   enums::NumberMode mode{}; | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
| @@ -1461,14 +1383,13 @@ class ListEntitiesNumberResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class NumberStateResponse : public ProtoMessage { | class NumberStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 50; |   static constexpr uint16_t MESSAGE_TYPE = 50; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 12; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "number_state_response"; } |   static constexpr const char *message_name() { return "number_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   float state{0.0f}; |   float state{0.0f}; | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -1499,21 +1420,14 @@ class NumberCommandRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesSelectResponse : public ProtoMessage { | class ListEntitiesSelectResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 52; |   static constexpr uint16_t MESSAGE_TYPE = 52; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 63; |   static constexpr uint16_t ESTIMATED_SIZE = 63; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_select_response"; } |   static constexpr const char *message_name() { return "list_entities_select_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   std::vector<std::string> options{}; |   std::vector<std::string> options{}; | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -1525,14 +1439,13 @@ class ListEntitiesSelectResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class SelectStateResponse : public ProtoMessage { | class SelectStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 53; |   static constexpr uint16_t MESSAGE_TYPE = 53; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 16; |   static constexpr uint16_t ESTIMATED_SIZE = 16; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "select_state_response"; } |   static constexpr const char *message_name() { return "select_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string state{}; |   std::string state{}; | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -1565,23 +1478,16 @@ class SelectCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesSirenResponse : public ProtoMessage { | class ListEntitiesSirenResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 55; |   static constexpr uint16_t MESSAGE_TYPE = 55; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 67; |   static constexpr uint16_t ESTIMATED_SIZE = 67; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_siren_response"; } |   static constexpr const char *message_name() { return "list_entities_siren_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   std::vector<std::string> tones{}; |   std::vector<std::string> tones{}; | ||||||
|   bool supports_duration{false}; |   bool supports_duration{false}; | ||||||
|   bool supports_volume{false}; |   bool supports_volume{false}; | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -1593,14 +1499,13 @@ class ListEntitiesSirenResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class SirenStateResponse : public ProtoMessage { | class SirenStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 56; |   static constexpr uint16_t MESSAGE_TYPE = 56; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 7; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "siren_state_response"; } |   static constexpr const char *message_name() { return "siren_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool state{false}; |   bool state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -1639,20 +1544,13 @@ class SirenCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesLockResponse : public ProtoMessage { | class ListEntitiesLockResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 58; |   static constexpr uint16_t MESSAGE_TYPE = 58; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 60; |   static constexpr uint16_t ESTIMATED_SIZE = 60; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_lock_response"; } |   static constexpr const char *message_name() { return "list_entities_lock_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   bool assumed_state{false}; |   bool assumed_state{false}; | ||||||
|   bool supports_open{false}; |   bool supports_open{false}; | ||||||
|   bool requires_code{false}; |   bool requires_code{false}; | ||||||
| @@ -1668,14 +1566,13 @@ class ListEntitiesLockResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class LockStateResponse : public ProtoMessage { | class LockStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 59; |   static constexpr uint16_t MESSAGE_TYPE = 59; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 7; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "lock_state_response"; } |   static constexpr const char *message_name() { return "lock_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   enums::LockState state{}; |   enums::LockState state{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -1709,20 +1606,13 @@ class LockCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesButtonResponse : public ProtoMessage { | class ListEntitiesButtonResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 61; |   static constexpr uint16_t MESSAGE_TYPE = 61; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 54; |   static constexpr uint16_t ESTIMATED_SIZE = 54; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_button_response"; } |   static constexpr const char *message_name() { return "list_entities_button_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -1769,20 +1659,13 @@ class MediaPlayerSupportedFormat : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesMediaPlayerResponse : public ProtoMessage { | class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 63; |   static constexpr uint16_t MESSAGE_TYPE = 63; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 81; |   static constexpr uint16_t ESTIMATED_SIZE = 81; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_media_player_response"; } |   static constexpr const char *message_name() { return "list_entities_media_player_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   bool supports_pause{false}; |   bool supports_pause{false}; | ||||||
|   std::vector<MediaPlayerSupportedFormat> supported_formats{}; |   std::vector<MediaPlayerSupportedFormat> supported_formats{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -1796,14 +1679,13 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class MediaPlayerStateResponse : public ProtoMessage { | class MediaPlayerStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 64; |   static constexpr uint16_t MESSAGE_TYPE = 64; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 14; |   static constexpr uint16_t ESTIMATED_SIZE = 14; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "media_player_state_response"; } |   static constexpr const char *message_name() { return "media_player_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   enums::MediaPlayerState state{}; |   enums::MediaPlayerState state{}; | ||||||
|   float volume{0.0f}; |   float volume{0.0f}; | ||||||
|   bool muted{false}; |   bool muted{false}; | ||||||
| @@ -2213,8 +2095,6 @@ class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "subscribe_bluetooth_connections_free_request"; } |   static constexpr const char *message_name() { return "subscribe_bluetooth_connections_free_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -2340,8 +2220,6 @@ class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "unsubscribe_bluetooth_le_advertisements_request"; } |   static constexpr const char *message_name() { return "unsubscribe_bluetooth_le_advertisements_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -2608,8 +2486,6 @@ class VoiceAssistantConfigurationRequest : public ProtoMessage { | |||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "voice_assistant_configuration_request"; } |   static constexpr const char *message_name() { return "voice_assistant_configuration_request"; } | ||||||
| #endif | #endif | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |  | ||||||
|   void calculate_size(uint32_t &total_size) const override; |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| #endif | #endif | ||||||
| @@ -2653,20 +2529,13 @@ class VoiceAssistantSetConfiguration : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { | class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 94; |   static constexpr uint16_t MESSAGE_TYPE = 94; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 53; |   static constexpr uint16_t ESTIMATED_SIZE = 53; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; } |   static constexpr const char *message_name() { return "list_entities_alarm_control_panel_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   uint32_t supported_features{0}; |   uint32_t supported_features{0}; | ||||||
|   bool requires_code{false}; |   bool requires_code{false}; | ||||||
|   bool requires_code_to_arm{false}; |   bool requires_code_to_arm{false}; | ||||||
| @@ -2681,14 +2550,13 @@ class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class AlarmControlPanelStateResponse : public ProtoMessage { | class AlarmControlPanelStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 95; |   static constexpr uint16_t MESSAGE_TYPE = 95; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 7; |   static constexpr uint16_t ESTIMATED_SIZE = 7; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "alarm_control_panel_state_response"; } |   static constexpr const char *message_name() { return "alarm_control_panel_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   enums::AlarmControlPanelState state{}; |   enums::AlarmControlPanelState state{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -2721,20 +2589,13 @@ class AlarmControlPanelCommandRequest : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesTextResponse : public ProtoMessage { | class ListEntitiesTextResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 97; |   static constexpr uint16_t MESSAGE_TYPE = 97; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 64; |   static constexpr uint16_t ESTIMATED_SIZE = 64; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_text_response"; } |   static constexpr const char *message_name() { return "list_entities_text_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   uint32_t min_length{0}; |   uint32_t min_length{0}; | ||||||
|   uint32_t max_length{0}; |   uint32_t max_length{0}; | ||||||
|   std::string pattern{}; |   std::string pattern{}; | ||||||
| @@ -2750,14 +2611,13 @@ class ListEntitiesTextResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class TextStateResponse : public ProtoMessage { | class TextStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 98; |   static constexpr uint16_t MESSAGE_TYPE = 98; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 16; |   static constexpr uint16_t ESTIMATED_SIZE = 16; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "text_state_response"; } |   static constexpr const char *message_name() { return "text_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string state{}; |   std::string state{}; | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -2790,20 +2650,13 @@ class TextCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesDateResponse : public ProtoMessage { | class ListEntitiesDateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 100; |   static constexpr uint16_t MESSAGE_TYPE = 100; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 45; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_date_response"; } |   static constexpr const char *message_name() { return "list_entities_date_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -2815,14 +2668,13 @@ class ListEntitiesDateResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class DateStateResponse : public ProtoMessage { | class DateStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 101; |   static constexpr uint16_t MESSAGE_TYPE = 101; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 19; |   static constexpr uint16_t ESTIMATED_SIZE = 19; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "date_state_response"; } |   static constexpr const char *message_name() { return "date_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   uint32_t year{0}; |   uint32_t year{0}; | ||||||
|   uint32_t month{0}; |   uint32_t month{0}; | ||||||
| @@ -2858,20 +2710,13 @@ class DateCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesTimeResponse : public ProtoMessage { | class ListEntitiesTimeResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 103; |   static constexpr uint16_t MESSAGE_TYPE = 103; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 45; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_time_response"; } |   static constexpr const char *message_name() { return "list_entities_time_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -2883,14 +2728,13 @@ class ListEntitiesTimeResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class TimeStateResponse : public ProtoMessage { | class TimeStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 104; |   static constexpr uint16_t MESSAGE_TYPE = 104; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 19; |   static constexpr uint16_t ESTIMATED_SIZE = 19; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "time_state_response"; } |   static constexpr const char *message_name() { return "time_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   uint32_t hour{0}; |   uint32_t hour{0}; | ||||||
|   uint32_t minute{0}; |   uint32_t minute{0}; | ||||||
| @@ -2926,20 +2770,13 @@ class TimeCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesEventResponse : public ProtoMessage { | class ListEntitiesEventResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 107; |   static constexpr uint16_t MESSAGE_TYPE = 107; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 72; |   static constexpr uint16_t ESTIMATED_SIZE = 72; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_event_response"; } |   static constexpr const char *message_name() { return "list_entities_event_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   std::vector<std::string> event_types{}; |   std::vector<std::string> event_types{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -2953,14 +2790,13 @@ class ListEntitiesEventResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class EventResponse : public ProtoMessage { | class EventResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 108; |   static constexpr uint16_t MESSAGE_TYPE = 108; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 14; |   static constexpr uint16_t ESTIMATED_SIZE = 14; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "event_response"; } |   static constexpr const char *message_name() { return "event_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string event_type{}; |   std::string event_type{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -2972,20 +2808,13 @@ class EventResponse : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesValveResponse : public ProtoMessage { | class ListEntitiesValveResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 109; |   static constexpr uint16_t MESSAGE_TYPE = 109; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 60; |   static constexpr uint16_t ESTIMATED_SIZE = 60; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_valve_response"; } |   static constexpr const char *message_name() { return "list_entities_valve_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   bool assumed_state{false}; |   bool assumed_state{false}; | ||||||
|   bool supports_position{false}; |   bool supports_position{false}; | ||||||
| @@ -3001,14 +2830,13 @@ class ListEntitiesValveResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ValveStateResponse : public ProtoMessage { | class ValveStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 110; |   static constexpr uint16_t MESSAGE_TYPE = 110; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 12; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "valve_state_response"; } |   static constexpr const char *message_name() { return "valve_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   float position{0.0f}; |   float position{0.0f}; | ||||||
|   enums::ValveOperation current_operation{}; |   enums::ValveOperation current_operation{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -3042,20 +2870,13 @@ class ValveCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesDateTimeResponse : public ProtoMessage { | class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 112; |   static constexpr uint16_t MESSAGE_TYPE = 112; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 45; |   static constexpr uint16_t ESTIMATED_SIZE = 45; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_date_time_response"; } |   static constexpr const char *message_name() { return "list_entities_date_time_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -3067,14 +2888,13 @@ class ListEntitiesDateTimeResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class DateTimeStateResponse : public ProtoMessage { | class DateTimeStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 113; |   static constexpr uint16_t MESSAGE_TYPE = 113; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 12; |   static constexpr uint16_t ESTIMATED_SIZE = 12; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "date_time_state_response"; } |   static constexpr const char *message_name() { return "date_time_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   uint32_t epoch_seconds{0}; |   uint32_t epoch_seconds{0}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| @@ -3105,20 +2925,13 @@ class DateTimeCommandRequest : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
| }; | }; | ||||||
| class ListEntitiesUpdateResponse : public ProtoMessage { | class ListEntitiesUpdateResponse : public InfoResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 116; |   static constexpr uint16_t MESSAGE_TYPE = 116; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 54; |   static constexpr uint16_t ESTIMATED_SIZE = 54; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "list_entities_update_response"; } |   static constexpr const char *message_name() { return "list_entities_update_response"; } | ||||||
| #endif | #endif | ||||||
|   std::string object_id{}; |  | ||||||
|   uint32_t key{0}; |  | ||||||
|   std::string name{}; |  | ||||||
|   std::string unique_id{}; |  | ||||||
|   std::string icon{}; |  | ||||||
|   bool disabled_by_default{false}; |  | ||||||
|   enums::EntityCategory entity_category{}; |  | ||||||
|   std::string device_class{}; |   std::string device_class{}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|   void calculate_size(uint32_t &total_size) const override; |   void calculate_size(uint32_t &total_size) const override; | ||||||
| @@ -3131,14 +2944,13 @@ class ListEntitiesUpdateResponse : public ProtoMessage { | |||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
| }; | }; | ||||||
| class UpdateStateResponse : public ProtoMessage { | class UpdateStateResponse : public StateResponseProtoMessage { | ||||||
|  public: |  public: | ||||||
|   static constexpr uint16_t MESSAGE_TYPE = 117; |   static constexpr uint16_t MESSAGE_TYPE = 117; | ||||||
|   static constexpr uint16_t ESTIMATED_SIZE = 61; |   static constexpr uint16_t ESTIMATED_SIZE = 61; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   static constexpr const char *message_name() { return "update_state_response"; } |   static constexpr const char *message_name() { return "update_state_response"; } | ||||||
| #endif | #endif | ||||||
|   uint32_t key{0}; |  | ||||||
|   bool missing_state{false}; |   bool missing_state{false}; | ||||||
|   bool in_progress{false}; |   bool in_progress{false}; | ||||||
|   bool has_progress{false}; |   bool has_progress{false}; | ||||||
|   | |||||||
| @@ -620,544 +620,300 @@ void APIServerConnection::on_ping_request(const PingRequest &msg) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) { | void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_connection_setup_()) { | ||||||
|     this->on_no_setup_connection(); |     DeviceInfoResponse ret = this->device_info(msg); | ||||||
|     return; |     if (!this->send_message(ret)) { | ||||||
|   } |       this->on_fatal_error(); | ||||||
|   DeviceInfoResponse ret = this->device_info(msg); |     } | ||||||
|   if (!this->send_message(ret)) { |  | ||||||
|     this->on_fatal_error(); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { | void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->list_entities(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->list_entities(msg); |  | ||||||
| } | } | ||||||
| void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) { | void APIServerConnection::on_subscribe_states_request(const SubscribeStatesRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->subscribe_states(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->subscribe_states(msg); |  | ||||||
| } | } | ||||||
| void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { | void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->subscribe_logs(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->subscribe_logs(msg); |  | ||||||
| } | } | ||||||
| void APIServerConnection::on_subscribe_homeassistant_services_request( | void APIServerConnection::on_subscribe_homeassistant_services_request( | ||||||
|     const SubscribeHomeassistantServicesRequest &msg) { |     const SubscribeHomeassistantServicesRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->subscribe_homeassistant_services(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->subscribe_homeassistant_services(msg); |  | ||||||
| } | } | ||||||
| void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { | void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->subscribe_home_assistant_states(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->subscribe_home_assistant_states(msg); |  | ||||||
| } | } | ||||||
| void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { | void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_connection_setup_()) { | ||||||
|     this->on_no_setup_connection(); |     GetTimeResponse ret = this->get_time(msg); | ||||||
|     return; |     if (!this->send_message(ret)) { | ||||||
|   } |       this->on_fatal_error(); | ||||||
|   GetTimeResponse ret = this->get_time(msg); |     } | ||||||
|   if (!this->send_message(ret)) { |  | ||||||
|     this->on_fatal_error(); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { | void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->execute_service(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->execute_service(msg); |  | ||||||
| } | } | ||||||
| #ifdef USE_API_NOISE | #ifdef USE_API_NOISE | ||||||
| void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { | void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg); | ||||||
|     return; |     if (!this->send_message(ret)) { | ||||||
|   } |       this->on_fatal_error(); | ||||||
|   if (!this->is_authenticated()) { |     } | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg); |  | ||||||
|   if (!this->send_message(ret)) { |  | ||||||
|     this->on_fatal_error(); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BUTTON | #ifdef USE_BUTTON | ||||||
| void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { | void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->button_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->button_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { | void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->camera_image(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->camera_image(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
| void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { | void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->climate_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->climate_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { | void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->cover_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->cover_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_DATETIME_DATE | #ifdef USE_DATETIME_DATE | ||||||
| void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { | void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->date_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->date_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_DATETIME_DATETIME | #ifdef USE_DATETIME_DATETIME | ||||||
| void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { | void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->datetime_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->datetime_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
| void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { | void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->fan_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->fan_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
| void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { | void APIServerConnection::on_light_command_request(const LightCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->light_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->light_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_LOCK | #ifdef USE_LOCK | ||||||
| void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { | void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->lock_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->lock_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
| void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { | void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->media_player_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->media_player_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { | void APIServerConnection::on_number_command_request(const NumberCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->number_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->number_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { | void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->select_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->select_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SIREN | #ifdef USE_SIREN | ||||||
| void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { | void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->siren_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->siren_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { | void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->switch_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->switch_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_TEXT | #ifdef USE_TEXT | ||||||
| void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { | void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->text_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->text_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_DATETIME_TIME | #ifdef USE_DATETIME_TIME | ||||||
| void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { | void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->time_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->time_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
| void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { | void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->update_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->update_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VALVE | #ifdef USE_VALVE | ||||||
| void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { | void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->valve_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->valve_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( | ||||||
|     const SubscribeBluetoothLEAdvertisementsRequest &msg) { |     const SubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->subscribe_bluetooth_le_advertisements(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->subscribe_bluetooth_le_advertisements(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { | void APIServerConnection::on_bluetooth_device_request(const BluetoothDeviceRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_device_request(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_device_request(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { | void APIServerConnection::on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_gatt_get_services(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_gatt_get_services(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { | void APIServerConnection::on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_gatt_read(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_gatt_read(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { | void APIServerConnection::on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_gatt_write(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_gatt_write(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { | void APIServerConnection::on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_gatt_read_descriptor(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_gatt_read_descriptor(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { | void APIServerConnection::on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_gatt_write_descriptor(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_gatt_write_descriptor(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { | void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_gatt_notify(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_gatt_notify(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_subscribe_bluetooth_connections_free_request( | void APIServerConnection::on_subscribe_bluetooth_connections_free_request( | ||||||
|     const SubscribeBluetoothConnectionsFreeRequest &msg) { |     const SubscribeBluetoothConnectionsFreeRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); | ||||||
|     return; |     if (!this->send_message(ret)) { | ||||||
|   } |       this->on_fatal_error(); | ||||||
|   if (!this->is_authenticated()) { |     } | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg); |  | ||||||
|   if (!this->send_message(ret)) { |  | ||||||
|     this->on_fatal_error(); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( | void APIServerConnection::on_unsubscribe_bluetooth_le_advertisements_request( | ||||||
|     const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { |     const UnsubscribeBluetoothLEAdvertisementsRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->unsubscribe_bluetooth_le_advertisements(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->unsubscribe_bluetooth_le_advertisements(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
| void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { | void APIServerConnection::on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->bluetooth_scanner_set_mode(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->bluetooth_scanner_set_mode(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
| void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { | void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->subscribe_voice_assistant(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->subscribe_voice_assistant(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
| void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { | void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); | ||||||
|     return; |     if (!this->send_message(ret)) { | ||||||
|   } |       this->on_fatal_error(); | ||||||
|   if (!this->is_authenticated()) { |     } | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); |  | ||||||
|   if (!this->send_message(ret)) { |  | ||||||
|     this->on_fatal_error(); |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_VOICE_ASSISTANT | #ifdef USE_VOICE_ASSISTANT | ||||||
| void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { | void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->voice_assistant_set_configuration(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->voice_assistant_set_configuration(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { | void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { | ||||||
|   if (!this->is_connection_setup()) { |   if (this->check_authenticated_()) { | ||||||
|     this->on_no_setup_connection(); |     this->alarm_control_panel_command(msg); | ||||||
|     return; |  | ||||||
|   } |   } | ||||||
|   if (!this->is_authenticated()) { |  | ||||||
|     this->on_unauthenticated_access(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   this->alarm_control_panel_command(msg); |  | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ void APIServer::setup() { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   this->last_connected_ = millis(); |   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()) { | ||||||
| @@ -164,7 +164,7 @@ void APIServer::loop() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->reboot_timeout_ != 0) { |   if (this->reboot_timeout_ != 0) { | ||||||
|     const uint32_t now = millis(); |     const uint32_t now = App.get_loop_component_start_time(); | ||||||
|     if (!this->is_connected()) { |     if (!this->is_connected()) { | ||||||
|       if (now - this->last_connected_ > this->reboot_timeout_) { |       if (now - this->last_connected_ > this->reboot_timeout_) { | ||||||
|         ESP_LOGE(TAG, "No client connected; rebooting"); |         ESP_LOGE(TAG, "No client connected; rebooting"); | ||||||
|   | |||||||
| @@ -142,19 +142,27 @@ class APIServer : public Component, public Controller { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool shutting_down_ = false; |   // Pointers and pointer-like types first (4 bytes each) | ||||||
|   std::unique_ptr<socket::Socket> socket_ = nullptr; |   std::unique_ptr<socket::Socket> socket_ = nullptr; | ||||||
|   uint16_t port_{6053}; |   Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>(); | ||||||
|  |   Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>(); | ||||||
|  |  | ||||||
|  |   // 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}; |   uint32_t last_connected_{0}; | ||||||
|  |  | ||||||
|  |   // Vectors and strings (12 bytes each on 32-bit) | ||||||
|   std::vector<std::unique_ptr<APIConnection>> clients_; |   std::vector<std::unique_ptr<APIConnection>> clients_; | ||||||
|   std::string password_; |   std::string password_; | ||||||
|   std::vector<uint8_t> shared_write_buffer_;  // Shared proto write buffer for all connections |   std::vector<uint8_t> shared_write_buffer_;  // Shared proto write buffer for all connections | ||||||
|   std::vector<HomeAssistantStateSubscription> state_subs_; |   std::vector<HomeAssistantStateSubscription> state_subs_; | ||||||
|   std::vector<UserServiceDescriptor *> user_services_; |   std::vector<UserServiceDescriptor *> user_services_; | ||||||
|   Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>(); |  | ||||||
|   Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>(); |   // Group smaller types together | ||||||
|  |   uint16_t port_{6053}; | ||||||
|  |   bool shutting_down_ = false; | ||||||
|  |   // 3 bytes used, 1 byte padding | ||||||
|  |  | ||||||
| #ifdef USE_API_NOISE | #ifdef USE_API_NOISE | ||||||
|   std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); |   std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>(); | ||||||
|   | |||||||
| @@ -216,7 +216,7 @@ class ProtoWriteBuffer { | |||||||
|     this->buffer_->insert(this->buffer_->end(), data, data + len); |     this->buffer_->insert(this->buffer_->end(), data, data + len); | ||||||
|   } |   } | ||||||
|   void encode_string(uint32_t field_id, const std::string &value, bool force = false) { |   void encode_string(uint32_t field_id, const std::string &value, bool force = false) { | ||||||
|     this->encode_string(field_id, value.data(), value.size()); |     this->encode_string(field_id, value.data(), value.size(), force); | ||||||
|   } |   } | ||||||
|   void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) { |   void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) { | ||||||
|     this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force); |     this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force); | ||||||
| @@ -327,9 +327,11 @@ class ProtoWriteBuffer { | |||||||
| class ProtoMessage { | class ProtoMessage { | ||||||
|  public: |  public: | ||||||
|   virtual ~ProtoMessage() = default; |   virtual ~ProtoMessage() = default; | ||||||
|   virtual void encode(ProtoWriteBuffer buffer) const = 0; |   // Default implementation for messages with no fields | ||||||
|  |   virtual void encode(ProtoWriteBuffer buffer) const {} | ||||||
|   void decode(const uint8_t *buffer, size_t length); |   void decode(const uint8_t *buffer, size_t length); | ||||||
|   virtual void calculate_size(uint32_t &total_size) const = 0; |   // Default implementation for messages with no fields | ||||||
|  |   virtual void calculate_size(uint32_t &total_size) const {} | ||||||
| #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; | ||||||
| @@ -377,6 +379,26 @@ class ProtoService { | |||||||
|     // Send the buffer |     // Send the buffer | ||||||
|     return this->send_buffer(buffer, message_type); |     return this->send_buffer(buffer, message_type); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Authentication helper methods | ||||||
|  |   bool check_connection_setup_() { | ||||||
|  |     if (!this->is_connection_setup()) { | ||||||
|  |       this->on_no_setup_connection(); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool check_authenticated_() { | ||||||
|  |     if (!this->check_connection_setup_()) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     if (!this->is_authenticated()) { | ||||||
|  |       this->on_unauthenticated_access(); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
|   | |||||||
| @@ -21,8 +21,8 @@ CONFIG_SCHEMA = cv.All( | |||||||
| @coroutine_with_priority(200.0) | @coroutine_with_priority(200.0) | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     if CORE.is_esp32 or CORE.is_libretiny: |     if CORE.is_esp32 or CORE.is_libretiny: | ||||||
|         # https://github.com/esphome/AsyncTCP/blob/master/library.json |         # https://github.com/ESP32Async/AsyncTCP | ||||||
|         cg.add_library("esphome/AsyncTCP-esphome", "2.1.4") |         cg.add_library("ESP32Async/AsyncTCP", "3.4.4") | ||||||
|     elif CORE.is_esp8266: |     elif CORE.is_esp8266: | ||||||
|         # https://github.com/esphome/ESPAsyncTCP |         # https://github.com/ESP32Async/ESPAsyncTCP | ||||||
|         cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") |         cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") | ||||||
|   | |||||||
| @@ -86,7 +86,7 @@ bool AudioTransferBuffer::reallocate(size_t new_buffer_size) { | |||||||
| bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) { | bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) { | ||||||
|   this->buffer_size_ = buffer_size; |   this->buffer_size_ = buffer_size; | ||||||
|  |  | ||||||
|   RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator; | ||||||
|  |  | ||||||
|   this->buffer_ = allocator.allocate(this->buffer_size_); |   this->buffer_ = allocator.allocate(this->buffer_size_); | ||||||
|   if (this->buffer_ == nullptr) { |   if (this->buffer_ == nullptr) { | ||||||
| @@ -101,7 +101,7 @@ bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) { | |||||||
|  |  | ||||||
| void AudioTransferBuffer::deallocate_buffer_() { | void AudioTransferBuffer::deallocate_buffer_() { | ||||||
|   if (this->buffer_ != nullptr) { |   if (this->buffer_ != nullptr) { | ||||||
|     RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |     RAMAllocator<uint8_t> allocator; | ||||||
|     allocator.deallocate(this->buffer_, this->buffer_size_); |     allocator.deallocate(this->buffer_, this->buffer_size_); | ||||||
|     this->buffer_ = nullptr; |     this->buffer_ = nullptr; | ||||||
|     this->data_start_ = nullptr; |     this->data_start_ = nullptr; | ||||||
|   | |||||||
| @@ -480,7 +480,11 @@ void BedJetHub::set_clock(uint8_t hour, uint8_t minute) { | |||||||
|  |  | ||||||
| /* Internal */ | /* Internal */ | ||||||
|  |  | ||||||
| void BedJetHub::loop() {} | void BedJetHub::loop() { | ||||||
|  |   // Parent BLEClientNode has a loop() method, but this component uses | ||||||
|  |   // polling via update() and BLE callbacks so loop isn't needed | ||||||
|  |   this->disable_loop(); | ||||||
|  | } | ||||||
| void BedJetHub::update() { this->dispatch_status_(); } | void BedJetHub::update() { this->dispatch_status_(); } | ||||||
|  |  | ||||||
| void BedJetHub::dump_config() { | void BedJetHub::dump_config() { | ||||||
|   | |||||||
| @@ -83,7 +83,11 @@ void BedJetClimate::reset_state_() { | |||||||
|   this->publish_state(); |   this->publish_state(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BedJetClimate::loop() {} | void BedJetClimate::loop() { | ||||||
|  |   // This component is controlled via the parent BedJetHub | ||||||
|  |   // Empty loop not needed, disable to save CPU cycles | ||||||
|  |   this->disable_loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BedJetClimate::control(const ClimateCall &call) { | void BedJetClimate::control(const ClimateCall &call) { | ||||||
|   ESP_LOGD(TAG, "Received BedJetClimate::control"); |   ESP_LOGD(TAG, "Received BedJetClimate::control"); | ||||||
|   | |||||||
| @@ -11,7 +11,11 @@ namespace ble_client { | |||||||
|  |  | ||||||
| static const char *const TAG = "ble_rssi_sensor"; | static const char *const TAG = "ble_rssi_sensor"; | ||||||
|  |  | ||||||
| void BLEClientRSSISensor::loop() {} | void BLEClientRSSISensor::loop() { | ||||||
|  |   // Parent BLEClientNode has a loop() method, but this component uses | ||||||
|  |   // polling via update() and BLE GAP callbacks so loop isn't needed | ||||||
|  |   this->disable_loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BLEClientRSSISensor::dump_config() { | void BLEClientRSSISensor::dump_config() { | ||||||
|   LOG_SENSOR("", "BLE Client RSSI Sensor", this); |   LOG_SENSOR("", "BLE Client RSSI Sensor", this); | ||||||
|   | |||||||
| @@ -11,7 +11,11 @@ namespace ble_client { | |||||||
|  |  | ||||||
| static const char *const TAG = "ble_sensor"; | static const char *const TAG = "ble_sensor"; | ||||||
|  |  | ||||||
| void BLESensor::loop() {} | void BLESensor::loop() { | ||||||
|  |   // Parent BLEClientNode has a loop() method, but this component uses | ||||||
|  |   // polling via update() and BLE callbacks so loop isn't needed | ||||||
|  |   this->disable_loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BLESensor::dump_config() { | void BLESensor::dump_config() { | ||||||
|   LOG_SENSOR("", "BLE Sensor", this); |   LOG_SENSOR("", "BLE Sensor", this); | ||||||
|   | |||||||
| @@ -14,7 +14,11 @@ static const char *const TAG = "ble_text_sensor"; | |||||||
|  |  | ||||||
| static const std::string EMPTY = ""; | static const std::string EMPTY = ""; | ||||||
|  |  | ||||||
| void BLETextSensor::loop() {} | void BLETextSensor::loop() { | ||||||
|  |   // Parent BLEClientNode has a loop() method, but this component uses | ||||||
|  |   // polling via update() and BLE callbacks so loop isn't needed | ||||||
|  |   this->disable_loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
| void BLETextSensor::dump_config() { | void BLETextSensor::dump_config() { | ||||||
|   LOG_TEXT_SENSOR("", "BLE Text Sensor", this); |   LOG_TEXT_SENSOR("", "BLE Text Sensor", this); | ||||||
|   | |||||||
| @@ -26,10 +26,17 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   friend class BluetoothProxy; |   friend class BluetoothProxy; | ||||||
|   bool seen_mtu_or_services_{false}; |  | ||||||
|  |  | ||||||
|   int16_t send_service_{-2}; |   // Memory optimized layout for 32-bit systems | ||||||
|  |   // Group 1: Pointers (4 bytes each, naturally aligned) | ||||||
|   BluetoothProxy *proxy_; |   BluetoothProxy *proxy_; | ||||||
|  |  | ||||||
|  |   // Group 2: 2-byte types | ||||||
|  |   int16_t send_service_{-2};  // Needs to handle negative values and service count | ||||||
|  |  | ||||||
|  |   // Group 3: 1-byte types | ||||||
|  |   bool seen_mtu_or_services_{false}; | ||||||
|  |   // 1 byte used, 1 byte padding | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace bluetooth_proxy | }  // namespace bluetooth_proxy | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() { | |||||||
|   return batch_buffer; |   return batch_buffer; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) { | bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) { | ||||||
|   if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) |   if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
| @@ -73,7 +73,7 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p | |||||||
|  |  | ||||||
|   // Add new advertisements to the batch buffer |   // Add new advertisements to the batch buffer | ||||||
|   for (size_t i = 0; i < count; i++) { |   for (size_t i = 0; i < count; i++) { | ||||||
|     auto &result = advertisements[i]; |     auto &result = scan_results[i]; | ||||||
|     uint8_t length = result.adv_data_len + result.scan_rsp_len; |     uint8_t length = result.adv_data_len + result.scan_rsp_len; | ||||||
|  |  | ||||||
|     batch_buffer.emplace_back(); |     batch_buffer.emplace_back(); | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com | |||||||
|  public: |  public: | ||||||
|   BluetoothProxy(); |   BluetoothProxy(); | ||||||
|   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; |   bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; | ||||||
|   bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override; |   bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
| @@ -134,11 +134,17 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com | |||||||
|  |  | ||||||
|   BluetoothConnection *get_connection_(uint64_t address, bool reserve); |   BluetoothConnection *get_connection_(uint64_t address, bool reserve); | ||||||
|  |  | ||||||
|   bool active_; |   // Memory optimized layout for 32-bit systems | ||||||
|  |   // Group 1: Pointers (4 bytes each, naturally aligned) | ||||||
|   std::vector<BluetoothConnection *> connections_{}; |  | ||||||
|   api::APIConnection *api_connection_{nullptr}; |   api::APIConnection *api_connection_{nullptr}; | ||||||
|  |  | ||||||
|  |   // Group 2: Container types (typically 12 bytes on 32-bit) | ||||||
|  |   std::vector<BluetoothConnection *> connections_{}; | ||||||
|  |  | ||||||
|  |   // Group 3: 1-byte types grouped together | ||||||
|  |   bool active_; | ||||||
|   bool raw_advertisements_{false}; |   bool raw_advertisements_{false}; | ||||||
|  |   // 2 bytes used, 2 bytes padding | ||||||
| }; | }; | ||||||
|  |  | ||||||
| extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | extern BluetoothProxy *global_bluetooth_proxy;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   | |||||||
| @@ -93,9 +93,8 @@ void BME280Component::setup() { | |||||||
|  |  | ||||||
|   // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries |   // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries | ||||||
|   // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. |   // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. | ||||||
|   if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { |   if (this->is_failed()) { | ||||||
|     this->component_state_ &= ~COMPONENT_STATE_MASK; |     this->reset_to_construction_state(); | ||||||
|     this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) { |   if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) { | ||||||
|   | |||||||
| @@ -12,8 +12,8 @@ from esphome.const import ( | |||||||
|     CONF_OVERSAMPLING, |     CONF_OVERSAMPLING, | ||||||
|     CONF_PRESSURE, |     CONF_PRESSURE, | ||||||
|     CONF_TEMPERATURE, |     CONF_TEMPERATURE, | ||||||
|     DEVICE_CLASS_HUMIDITY, |  | ||||||
|     DEVICE_CLASS_ATMOSPHERIC_PRESSURE, |     DEVICE_CLASS_ATMOSPHERIC_PRESSURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |     DEVICE_CLASS_TEMPERATURE, | ||||||
|     ICON_GAS_CYLINDER, |     ICON_GAS_CYLINDER, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import re | import re | ||||||
|  |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     if CORE.using_arduino: |     if CORE.using_arduino: | ||||||
|         if CORE.is_esp32: |         if CORE.is_esp32: | ||||||
|  |             cg.add_library("ESP32 Async UDP", None) | ||||||
|             cg.add_library("DNSServer", None) |             cg.add_library("DNSServer", None) | ||||||
|             cg.add_library("WiFi", None) |             cg.add_library("WiFi", None) | ||||||
|         if CORE.is_esp8266: |         if CORE.is_esp8266: | ||||||
|   | |||||||
| @@ -37,7 +37,12 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { | |||||||
|   request->redirect("/?save"); |   request->redirect("/?save"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void CaptivePortal::setup() {} | void CaptivePortal::setup() { | ||||||
|  | #ifndef USE_ARDUINO | ||||||
|  |   // No DNS server needed for non-Arduino frameworks | ||||||
|  |   this->disable_loop(); | ||||||
|  | #endif | ||||||
|  | } | ||||||
| void CaptivePortal::start() { | void CaptivePortal::start() { | ||||||
|   this->base_->init(); |   this->base_->init(); | ||||||
|   if (!this->initialized_) { |   if (!this->initialized_) { | ||||||
| @@ -50,6 +55,8 @@ void CaptivePortal::start() { | |||||||
|   this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); |   this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); | ||||||
|   network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); |   network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); | ||||||
|   this->dns_server_->start(53, "*", ip); |   this->dns_server_->start(53, "*", ip); | ||||||
|  |   // Re-enable loop() when DNS server is started | ||||||
|  |   this->enable_loop(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { |   this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { | ||||||
| @@ -68,7 +75,11 @@ void CaptivePortal::start() { | |||||||
|  |  | ||||||
| void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { | void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { | ||||||
|   if (req->url() == "/") { |   if (req->url() == "/") { | ||||||
|  | #ifndef USE_ESP8266 | ||||||
|  |     auto *response = req->beginResponse(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); | ||||||
|  | #else | ||||||
|     auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); |     auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); | ||||||
|  | #endif | ||||||
|     response->addHeader("Content-Encoding", "gzip"); |     response->addHeader("Content-Encoding", "gzip"); | ||||||
|     req->send(response); |     req->send(response); | ||||||
|     return; |     return; | ||||||
|   | |||||||
| @@ -21,8 +21,11 @@ class CaptivePortal : public AsyncWebHandler, public Component { | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
|   void loop() override { |   void loop() override { | ||||||
|     if (this->dns_server_ != nullptr) |     if (this->dns_server_ != nullptr) { | ||||||
|       this->dns_server_->processNextRequest(); |       this->dns_server_->processNextRequest(); | ||||||
|  |     } else { | ||||||
|  |       this->disable_loop(); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
| @@ -37,7 +40,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { | |||||||
| #endif | #endif | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool canHandle(AsyncWebServerRequest *request) override { |   bool canHandle(AsyncWebServerRequest *request) const override { | ||||||
|     if (!this->active_) |     if (!this->active_) | ||||||
|       return false; |       return false; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| """CM1106 Sensor component for ESPHome.""" | """CM1106 Sensor component for ESPHome.""" | ||||||
|  |  | ||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.automation import maybe_simple_id | from esphome.automation import maybe_simple_id | ||||||
|  | import esphome.codegen as cg | ||||||
| from esphome.components import sensor, uart | from esphome.components import sensor, uart | ||||||
|  | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_CO2, |     CONF_CO2, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|   | |||||||
| @@ -11,25 +11,25 @@ static const char *const TAG = "datetime.date_entity"; | |||||||
|  |  | ||||||
| void DateEntity::publish_state() { | void DateEntity::publish_state() { | ||||||
|   if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) { |   if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->year_ < 1970 || this->year_ > 3000) { |   if (this->year_ < 1970 || this->year_ > 3000) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Year must be between 1970 and 3000"); |     ESP_LOGE(TAG, "Year must be between 1970 and 3000"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->month_ < 1 || this->month_ > 12) { |   if (this->month_ < 1 || this->month_ > 12) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Month must be between 1 and 12"); |     ESP_LOGE(TAG, "Month must be between 1 and 12"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->day_ > days_in_month(this->month_, this->year_)) { |   if (this->day_ > days_in_month(this->month_, this->year_)) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_); |     ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->has_state_ = true; |   this->set_has_state(true); | ||||||
|   ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); |   ESP_LOGD(TAG, "'%s': Sending date %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_); | ||||||
|   this->state_callback_.call(); |   this->state_callback_.call(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,9 +13,6 @@ namespace datetime { | |||||||
|  |  | ||||||
| class DateTimeBase : public EntityBase { | class DateTimeBase : public EntityBase { | ||||||
|  public: |  public: | ||||||
|   /// Return whether this Datetime has gotten a full state yet. |  | ||||||
|   bool has_state() const { return this->has_state_; } |  | ||||||
|  |  | ||||||
|   virtual ESPTime state_as_esptime() const = 0; |   virtual ESPTime state_as_esptime() const = 0; | ||||||
|  |  | ||||||
|   void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } |   void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } | ||||||
| @@ -31,8 +28,6 @@ class DateTimeBase : public EntityBase { | |||||||
| #ifdef USE_TIME | #ifdef USE_TIME | ||||||
|   time::RealTimeClock *rtc_; |   time::RealTimeClock *rtc_; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   bool has_state_{false}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #ifdef USE_TIME | #ifdef USE_TIME | ||||||
|   | |||||||
| @@ -11,40 +11,40 @@ static const char *const TAG = "datetime.datetime_entity"; | |||||||
|  |  | ||||||
| void DateTimeEntity::publish_state() { | void DateTimeEntity::publish_state() { | ||||||
|   if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) { |   if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->year_ < 1970 || this->year_ > 3000) { |   if (this->year_ < 1970 || this->year_ > 3000) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Year must be between 1970 and 3000"); |     ESP_LOGE(TAG, "Year must be between 1970 and 3000"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->month_ < 1 || this->month_ > 12) { |   if (this->month_ < 1 || this->month_ > 12) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Month must be between 1 and 12"); |     ESP_LOGE(TAG, "Month must be between 1 and 12"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->day_ > days_in_month(this->month_, this->year_)) { |   if (this->day_ > days_in_month(this->month_, this->year_)) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_); |     ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->hour_ > 23) { |   if (this->hour_ > 23) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Hour must be between 0 and 23"); |     ESP_LOGE(TAG, "Hour must be between 0 and 23"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->minute_ > 59) { |   if (this->minute_ > 59) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Minute must be between 0 and 59"); |     ESP_LOGE(TAG, "Minute must be between 0 and 59"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->second_ > 59) { |   if (this->second_ > 59) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Second must be between 0 and 59"); |     ESP_LOGE(TAG, "Second must be between 0 and 59"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->has_state_ = true; |   this->set_has_state(true); | ||||||
|   ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, |   ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, | ||||||
|            this->month_, this->day_, this->hour_, this->minute_, this->second_); |            this->month_, this->day_, this->hour_, this->minute_, this->second_); | ||||||
|   this->state_callback_.call(); |   this->state_callback_.call(); | ||||||
|   | |||||||
| @@ -11,21 +11,21 @@ static const char *const TAG = "datetime.time_entity"; | |||||||
|  |  | ||||||
| void TimeEntity::publish_state() { | void TimeEntity::publish_state() { | ||||||
|   if (this->hour_ > 23) { |   if (this->hour_ > 23) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Hour must be between 0 and 23"); |     ESP_LOGE(TAG, "Hour must be between 0 and 23"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->minute_ > 59) { |   if (this->minute_ > 59) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Minute must be between 0 and 59"); |     ESP_LOGE(TAG, "Minute must be between 0 and 59"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (this->second_ > 59) { |   if (this->second_ > 59) { | ||||||
|     this->has_state_ = false; |     this->set_has_state(false); | ||||||
|     ESP_LOGE(TAG, "Second must be between 0 and 59"); |     ESP_LOGE(TAG, "Second must be between 0 and 59"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->has_state_ = true; |   this->set_has_state(true); | ||||||
|   ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, |   ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, | ||||||
|            this->second_); |            this->second_); | ||||||
|   this->state_callback_.call(); |   this->state_callback_.call(); | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ namespace display { | |||||||
| static const char *const TAG = "display"; | static const char *const TAG = "display"; | ||||||
|  |  | ||||||
| void DisplayBuffer::init_internal_(uint32_t buffer_length) { | void DisplayBuffer::init_internal_(uint32_t buffer_length) { | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator; | ||||||
|   this->buffer_ = allocator.allocate(buffer_length); |   this->buffer_ = allocator.allocate(buffer_length); | ||||||
|   if (this->buffer_ == nullptr) { |   if (this->buffer_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Could not allocate buffer for display!"); |     ESP_LOGE(TAG, "Could not allocate buffer for display!"); | ||||||
|   | |||||||
| @@ -94,6 +94,13 @@ COMPILER_OPTIMIZATIONS = { | |||||||
|     "SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE", |     "SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE", | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ARDUINO_ALLOWED_VARIANTS = [ | ||||||
|  |     VARIANT_ESP32, | ||||||
|  |     VARIANT_ESP32C3, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_cpu_frequencies(*frequencies): | def get_cpu_frequencies(*frequencies): | ||||||
|     return [str(x) + "MHZ" for x in frequencies] |     return [str(x) + "MHZ" for x in frequencies] | ||||||
| @@ -125,6 +132,8 @@ def set_core_data(config): | |||||||
|         choices = CPU_FREQUENCIES[variant] |         choices = CPU_FREQUENCIES[variant] | ||||||
|         if "160MHZ" in choices: |         if "160MHZ" in choices: | ||||||
|             cpu_frequency = "160MHZ" |             cpu_frequency = "160MHZ" | ||||||
|  |         elif "360MHZ" in choices: | ||||||
|  |             cpu_frequency = "360MHZ" | ||||||
|         else: |         else: | ||||||
|             cpu_frequency = choices[-1] |             cpu_frequency = choices[-1] | ||||||
|         config[CONF_CPU_FREQUENCY] = cpu_frequency |         config[CONF_CPU_FREQUENCY] = cpu_frequency | ||||||
| @@ -143,12 +152,17 @@ def set_core_data(config): | |||||||
|         CORE.data[KEY_ESP32][KEY_COMPONENTS] = {} |         CORE.data[KEY_ESP32][KEY_COMPONENTS] = {} | ||||||
|     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: |     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||||
|         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" |         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" | ||||||
|  |         if variant not in ARDUINO_ALLOWED_VARIANTS: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 f"ESPHome does not support using the Arduino framework for the {variant}. Please use the ESP-IDF framework instead.", | ||||||
|  |                 path=[CONF_FRAMEWORK, CONF_TYPE], | ||||||
|  |             ) | ||||||
|     CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( |     CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( | ||||||
|         config[CONF_FRAMEWORK][CONF_VERSION] |         config[CONF_FRAMEWORK][CONF_VERSION] | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] |     CORE.data[KEY_ESP32][KEY_BOARD] = config[CONF_BOARD] | ||||||
|     CORE.data[KEY_ESP32][KEY_VARIANT] = config[CONF_VARIANT] |     CORE.data[KEY_ESP32][KEY_VARIANT] = variant | ||||||
|     CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {} |     CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES] = {} | ||||||
|  |  | ||||||
|     return config |     return config | ||||||
| @@ -277,11 +291,8 @@ def add_extra_build_file(filename: str, path: str) -> bool: | |||||||
|  |  | ||||||
| def _format_framework_arduino_version(ver: cv.Version) -> str: | def _format_framework_arduino_version(ver: cv.Version) -> str: | ||||||
|     # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to |     # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to | ||||||
|     # a PIO platformio/framework-arduinoespressif32 value |     # a PIO pioarduino/framework-arduinoespressif32 value | ||||||
|     # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 |     return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{str(ver)}/esp32-{str(ver)}.zip" | ||||||
|     if ver <= cv.Version(1, 0, 3): |  | ||||||
|         return f"~2.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" |  | ||||||
|     return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _format_framework_espidf_version( | def _format_framework_espidf_version( | ||||||
| @@ -305,12 +316,10 @@ def _format_framework_espidf_version( | |||||||
|  |  | ||||||
| # The default/recommended arduino framework version | # The default/recommended arduino framework version | ||||||
| #  - https://github.com/espressif/arduino-esp32/releases | #  - https://github.com/espressif/arduino-esp32/releases | ||||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif32 | RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 1, 3) | ||||||
| RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5) | # The platform-espressif32 version to use for arduino frameworks | ||||||
| # The platformio/espressif32 version to use for arduino frameworks | #  - https://github.com/pioarduino/platform-espressif32/releases | ||||||
| #  - https://github.com/platformio/platform-espressif32/releases | ARDUINO_PLATFORM_VERSION = cv.Version(53, 3, 13) | ||||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 |  | ||||||
| ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0) |  | ||||||
|  |  | ||||||
| # The default/recommended esp-idf framework version | # The default/recommended esp-idf framework version | ||||||
| #  - https://github.com/espressif/esp-idf/releases | #  - https://github.com/espressif/esp-idf/releases | ||||||
| @@ -353,8 +362,8 @@ SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ | |||||||
| def _arduino_check_versions(value): | def _arduino_check_versions(value): | ||||||
|     value = value.copy() |     value = value.copy() | ||||||
|     lookups = { |     lookups = { | ||||||
|         "dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"), |         "dev": (cv.Version(3, 1, 3), "https://github.com/espressif/arduino-esp32.git"), | ||||||
|         "latest": (cv.Version(2, 0, 9), None), |         "latest": (cv.Version(3, 1, 3), None), | ||||||
|         "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), |         "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -376,6 +385,10 @@ def _arduino_check_versions(value): | |||||||
|         CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) |         CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     if value[CONF_SOURCE].startswith("http"): | ||||||
|  |         # prefix is necessary or platformio will complain with a cryptic error | ||||||
|  |         value[CONF_SOURCE] = f"framework-arduinoespressif32@{value[CONF_SOURCE]}" | ||||||
|  |  | ||||||
|     if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: |     if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: | ||||||
|         _LOGGER.warning( |         _LOGGER.warning( | ||||||
|             "The selected Arduino framework version is not the recommended one. " |             "The selected Arduino framework version is not the recommended one. " | ||||||
| @@ -618,6 +631,21 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _set_default_framework(config): | ||||||
|  |     if CONF_FRAMEWORK not in config: | ||||||
|  |         config = config.copy() | ||||||
|  |  | ||||||
|  |         variant = config[CONF_VARIANT] | ||||||
|  |         if variant in ARDUINO_ALLOWED_VARIANTS: | ||||||
|  |             config[CONF_FRAMEWORK] = ARDUINO_FRAMEWORK_SCHEMA({}) | ||||||
|  |             config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ARDUINO | ||||||
|  |         else: | ||||||
|  |             config[CONF_FRAMEWORK] = ESP_IDF_FRAMEWORK_SCHEMA({}) | ||||||
|  |             config[CONF_FRAMEWORK][CONF_TYPE] = FRAMEWORK_ESP_IDF | ||||||
|  |  | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| FRAMEWORK_ESP_IDF = "esp-idf" | FRAMEWORK_ESP_IDF = "esp-idf" | ||||||
| FRAMEWORK_ARDUINO = "arduino" | FRAMEWORK_ARDUINO = "arduino" | ||||||
| FRAMEWORK_SCHEMA = cv.typed_schema( | FRAMEWORK_SCHEMA = cv.typed_schema( | ||||||
| @@ -627,7 +655,6 @@ FRAMEWORK_SCHEMA = cv.typed_schema( | |||||||
|     }, |     }, | ||||||
|     lower=True, |     lower=True, | ||||||
|     space="-", |     space="-", | ||||||
|     default_type=FRAMEWORK_ARDUINO, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -654,10 +681,11 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_PARTITIONS): cv.file_, |             cv.Optional(CONF_PARTITIONS): cv.file_, | ||||||
|             cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True), |             cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True), | ||||||
|             cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA, |             cv.Optional(CONF_FRAMEWORK): FRAMEWORK_SCHEMA, | ||||||
|         } |         } | ||||||
|     ), |     ), | ||||||
|     _detect_variant, |     _detect_variant, | ||||||
|  |     _set_default_framework, | ||||||
|     set_core_data, |     set_core_data, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -668,6 +696,7 @@ FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     cg.add_platformio_option("board", config[CONF_BOARD]) |     cg.add_platformio_option("board", config[CONF_BOARD]) | ||||||
|     cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE]) |     cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE]) | ||||||
|  |     cg.set_cpp_standard("gnu++17") | ||||||
|     cg.add_build_flag("-DUSE_ESP32") |     cg.add_build_flag("-DUSE_ESP32") | ||||||
|     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) |     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) | ||||||
|     cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") |     cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}") | ||||||
| @@ -801,10 +830,7 @@ async def to_code(config): | |||||||
|         cg.add_platformio_option("framework", "arduino") |         cg.add_platformio_option("framework", "arduino") | ||||||
|         cg.add_build_flag("-DUSE_ARDUINO") |         cg.add_build_flag("-DUSE_ARDUINO") | ||||||
|         cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") |         cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") | ||||||
|         cg.add_platformio_option( |         cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) | ||||||
|             "platform_packages", |  | ||||||
|             [f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"], |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         if CONF_PARTITIONS in config: |         if CONF_PARTITIONS in config: | ||||||
|             cg.add_platformio_option("board_build.partitions", config[CONF_PARTITIONS]) |             cg.add_platformio_option("board_build.partitions", config[CONF_PARTITIONS]) | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include "ble.h" | #include "ble.h" | ||||||
|  | #include "ble_event_pool.h" | ||||||
|  |  | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| @@ -23,9 +24,6 @@ namespace esp32_ble { | |||||||
|  |  | ||||||
| static const char *const TAG = "esp32_ble"; | static const char *const TAG = "esp32_ble"; | ||||||
|  |  | ||||||
| static RAMAllocator<BLEEvent> EVENT_ALLOCATOR(  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) |  | ||||||
|     RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL); |  | ||||||
|  |  | ||||||
| void ESP32BLE::setup() { | void ESP32BLE::setup() { | ||||||
|   global_ble = this; |   global_ble = this; | ||||||
|   ESP_LOGCONFIG(TAG, "Running setup"); |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
| @@ -304,82 +302,191 @@ void ESP32BLE::loop() { | |||||||
|   BLEEvent *ble_event = this->ble_events_.pop(); |   BLEEvent *ble_event = this->ble_events_.pop(); | ||||||
|   while (ble_event != nullptr) { |   while (ble_event != nullptr) { | ||||||
|     switch (ble_event->type_) { |     switch (ble_event->type_) { | ||||||
|       case BLEEvent::GATTS: |       case BLEEvent::GATTS: { | ||||||
|         this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if, |         esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event; | ||||||
|                                         &ble_event->event_.gatts.gatts_param); |         esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if; | ||||||
|  |         esp_ble_gatts_cb_param_t *param = ble_event->event_.gatts.gatts_param; | ||||||
|  |         ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event); | ||||||
|  |         for (auto *gatts_handler : this->gatts_event_handlers_) { | ||||||
|  |           gatts_handler->gatts_event_handler(event, gatts_if, param); | ||||||
|  |         } | ||||||
|         break; |         break; | ||||||
|       case BLEEvent::GATTC: |       } | ||||||
|         this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if, |       case BLEEvent::GATTC: { | ||||||
|                                         &ble_event->event_.gattc.gattc_param); |         esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event; | ||||||
|  |         esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if; | ||||||
|  |         esp_ble_gattc_cb_param_t *param = ble_event->event_.gattc.gattc_param; | ||||||
|  |         ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event); | ||||||
|  |         for (auto *gattc_handler : this->gattc_event_handlers_) { | ||||||
|  |           gattc_handler->gattc_event_handler(event, gattc_if, param); | ||||||
|  |         } | ||||||
|         break; |         break; | ||||||
|       case BLEEvent::GAP: |       } | ||||||
|         this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); |       case BLEEvent::GAP: { | ||||||
|  |         esp_gap_ble_cb_event_t gap_event = ble_event->event_.gap.gap_event; | ||||||
|  |         switch (gap_event) { | ||||||
|  |           case ESP_GAP_BLE_SCAN_RESULT_EVT: | ||||||
|  |             // Use the new scan event handler - no memcpy! | ||||||
|  |             for (auto *scan_handler : this->gap_scan_event_handlers_) { | ||||||
|  |               scan_handler->gap_scan_event_handler(ble_event->scan_result()); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |           // Scan complete events | ||||||
|  |           case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: | ||||||
|  |           case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: | ||||||
|  |           case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: | ||||||
|  |             // All three scan complete events have the same structure with just status | ||||||
|  |             // The scan_complete struct matches ESP-IDF's layout exactly, so this reinterpret_cast is safe | ||||||
|  |             // This is verified at compile-time by static_assert checks in ble_event.h | ||||||
|  |             // The struct already contains our copy of the status (copied in BLEEvent constructor) | ||||||
|  |             ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); | ||||||
|  |             for (auto *gap_handler : this->gap_event_handlers_) { | ||||||
|  |               gap_handler->gap_event_handler( | ||||||
|  |                   gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.scan_complete)); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |           // Advertising complete events | ||||||
|  |           case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: | ||||||
|  |           case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: | ||||||
|  |           case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: | ||||||
|  |           case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: | ||||||
|  |           case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: | ||||||
|  |             // All advertising complete events have the same structure with just status | ||||||
|  |             ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); | ||||||
|  |             for (auto *gap_handler : this->gap_event_handlers_) { | ||||||
|  |               gap_handler->gap_event_handler( | ||||||
|  |                   gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.adv_complete)); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |           // RSSI complete event | ||||||
|  |           case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: | ||||||
|  |             ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); | ||||||
|  |             for (auto *gap_handler : this->gap_event_handlers_) { | ||||||
|  |               gap_handler->gap_event_handler( | ||||||
|  |                   gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.read_rssi_complete)); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |           // Security events | ||||||
|  |           case ESP_GAP_BLE_AUTH_CMPL_EVT: | ||||||
|  |           case ESP_GAP_BLE_SEC_REQ_EVT: | ||||||
|  |           case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: | ||||||
|  |           case ESP_GAP_BLE_PASSKEY_REQ_EVT: | ||||||
|  |           case ESP_GAP_BLE_NC_REQ_EVT: | ||||||
|  |             ESP_LOGV(TAG, "gap_event_handler - %d", gap_event); | ||||||
|  |             for (auto *gap_handler : this->gap_event_handlers_) { | ||||||
|  |               gap_handler->gap_event_handler( | ||||||
|  |                   gap_event, reinterpret_cast<esp_ble_gap_cb_param_t *>(&ble_event->event_.gap.security)); | ||||||
|  |             } | ||||||
|  |             break; | ||||||
|  |  | ||||||
|  |           default: | ||||||
|  |             // Unknown/unhandled event | ||||||
|  |             ESP_LOGW(TAG, "Unhandled GAP event type in loop: %d", gap_event); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|         break; |         break; | ||||||
|  |       } | ||||||
|       default: |       default: | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|     ble_event->~BLEEvent(); |     // Return the event to the pool | ||||||
|     EVENT_ALLOCATOR.deallocate(ble_event, 1); |     this->ble_event_pool_.release(ble_event); | ||||||
|     ble_event = this->ble_events_.pop(); |     ble_event = this->ble_events_.pop(); | ||||||
|   } |   } | ||||||
|   if (this->advertising_ != nullptr) { |   if (this->advertising_ != nullptr) { | ||||||
|     this->advertising_->loop(); |     this->advertising_->loop(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Log dropped events periodically | ||||||
|  |   uint16_t dropped = this->ble_events_.get_and_reset_dropped_count(); | ||||||
|  |   if (dropped > 0) { | ||||||
|  |     ESP_LOGW(TAG, "Dropped %u BLE events due to buffer overflow", dropped); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | // Helper function to load new event data based on type | ||||||
|   BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); | void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { | ||||||
|   if (new_event == nullptr) { |   event->load_gap_event(e, p); | ||||||
|     // Memory too fragmented to allocate new event. Can only drop it until memory comes back | } | ||||||
|  |  | ||||||
|  | void load_ble_event(BLEEvent *event, esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { | ||||||
|  |   event->load_gattc_event(e, i, p); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void load_ble_event(BLEEvent *event, esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { | ||||||
|  |   event->load_gatts_event(e, i, p); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<typename... Args> void enqueue_ble_event(Args... args) { | ||||||
|  |   // Allocate an event from the pool | ||||||
|  |   BLEEvent *event = global_ble->ble_event_pool_.allocate(); | ||||||
|  |   if (event == nullptr) { | ||||||
|  |     // No events available - queue is full or we're out of memory | ||||||
|  |     global_ble->ble_events_.increment_dropped_count(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   new (new_event) BLEEvent(event, param); |  | ||||||
|   global_ble->ble_events_.push(new_event); |  | ||||||
| }  // NOLINT(clang-analyzer-unix.Malloc) |  | ||||||
|  |  | ||||||
| void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { |   // Load new event data (replaces previous event) | ||||||
|   ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event); |   load_ble_event(event, args...); | ||||||
|   for (auto *gap_handler : this->gap_event_handlers_) { |  | ||||||
|     gap_handler->gap_event_handler(event, param); |   // Push the event to the queue | ||||||
|  |   global_ble->ble_events_.push(event); | ||||||
|  |   // Push always succeeds because we're the only producer and the pool ensures we never exceed queue size | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Explicit template instantiations for the friend function | ||||||
|  | template void enqueue_ble_event(esp_gap_ble_cb_event_t, esp_ble_gap_cb_param_t *); | ||||||
|  | template void enqueue_ble_event(esp_gatts_cb_event_t, esp_gatt_if_t, esp_ble_gatts_cb_param_t *); | ||||||
|  | template void enqueue_ble_event(esp_gattc_cb_event_t, esp_gatt_if_t, esp_ble_gattc_cb_param_t *); | ||||||
|  |  | ||||||
|  | void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|  |   switch (event) { | ||||||
|  |     // Queue GAP events that components need to handle | ||||||
|  |     // Scanning events - used by esp32_ble_tracker | ||||||
|  |     case ESP_GAP_BLE_SCAN_RESULT_EVT: | ||||||
|  |     case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: | ||||||
|  |     case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: | ||||||
|  |     case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: | ||||||
|  |     // Advertising events - used by esp32_ble_beacon and esp32_ble server | ||||||
|  |     case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: | ||||||
|  |     case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: | ||||||
|  |     case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: | ||||||
|  |     case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: | ||||||
|  |     case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: | ||||||
|  |     // Connection events - used by ble_client | ||||||
|  |     case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: | ||||||
|  |     // Security events - used by ble_client and bluetooth_proxy | ||||||
|  |     case ESP_GAP_BLE_AUTH_CMPL_EVT: | ||||||
|  |     case ESP_GAP_BLE_SEC_REQ_EVT: | ||||||
|  |     case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: | ||||||
|  |     case ESP_GAP_BLE_PASSKEY_REQ_EVT: | ||||||
|  |     case ESP_GAP_BLE_NC_REQ_EVT: | ||||||
|  |       enqueue_ble_event(event, param); | ||||||
|  |       return; | ||||||
|  |  | ||||||
|  |     // Ignore these GAP events as they are not relevant for our use case | ||||||
|  |     case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: | ||||||
|  |     case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: | ||||||
|  |       return; | ||||||
|  |  | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|   } |   } | ||||||
|  |   ESP_LOGW(TAG, "Ignoring unexpected GAP event type: %d", event); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | ||||||
|                                    esp_ble_gatts_cb_param_t *param) { |                                    esp_ble_gatts_cb_param_t *param) { | ||||||
|   BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); |   enqueue_ble_event(event, gatts_if, param); | ||||||
|   if (new_event == nullptr) { |  | ||||||
|     // Memory too fragmented to allocate new event. Can only drop it until memory comes back |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   new (new_event) BLEEvent(event, gatts_if, param); |  | ||||||
|   global_ble->ble_events_.push(new_event); |  | ||||||
| }  // NOLINT(clang-analyzer-unix.Malloc) |  | ||||||
|  |  | ||||||
| void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, |  | ||||||
|                                          esp_ble_gatts_cb_param_t *param) { |  | ||||||
|   ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event); |  | ||||||
|   for (auto *gatts_handler : this->gatts_event_handlers_) { |  | ||||||
|     gatts_handler->gatts_event_handler(event, gatts_if, param); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                    esp_ble_gattc_cb_param_t *param) { |                                    esp_ble_gattc_cb_param_t *param) { | ||||||
|   BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1); |   enqueue_ble_event(event, gattc_if, param); | ||||||
|   if (new_event == nullptr) { |  | ||||||
|     // Memory too fragmented to allocate new event. Can only drop it until memory comes back |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   new (new_event) BLEEvent(event, gattc_if, param); |  | ||||||
|   global_ble->ble_events_.push(new_event); |  | ||||||
| }  // NOLINT(clang-analyzer-unix.Malloc) |  | ||||||
|  |  | ||||||
| void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |  | ||||||
|                                          esp_ble_gattc_cb_param_t *param) { |  | ||||||
|   ESP_LOGV(TAG, "(BLE) gattc_event [esp_gatt_if: %d] - %d", gattc_if, event); |  | ||||||
|   for (auto *gattc_handler : this->gattc_event_handlers_) { |  | ||||||
|     gattc_handler->gattc_event_handler(event, gattc_if, param); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } | float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| #include "ble_advertising.h" | #include "ble_advertising.h" | ||||||
| #include "ble_uuid.h" | #include "ble_uuid.h" | ||||||
|  | #include "ble_scan_result.h" | ||||||
|  |  | ||||||
| #include <functional> | #include <functional> | ||||||
|  |  | ||||||
| @@ -11,6 +12,7 @@ | |||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #include "ble_event.h" | #include "ble_event.h" | ||||||
|  | #include "ble_event_pool.h" | ||||||
| #include "queue.h" | #include "queue.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
| @@ -22,6 +24,16 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_ble { | namespace esp32_ble { | ||||||
|  |  | ||||||
|  | // Maximum number of BLE scan results to buffer | ||||||
|  | #ifdef USE_PSRAM | ||||||
|  | static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 32; | ||||||
|  | #else | ||||||
|  | static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 20; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | // Maximum size of the BLE event queue - must be power of 2 for lock-free queue | ||||||
|  | static constexpr size_t MAX_BLE_QUEUE_SIZE = 64; | ||||||
|  |  | ||||||
| uint64_t ble_addr_to_uint64(const esp_bd_addr_t address); | uint64_t ble_addr_to_uint64(const esp_bd_addr_t address); | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(modernize-use-using) | // NOLINTNEXTLINE(modernize-use-using) | ||||||
| @@ -57,6 +69,11 @@ class GAPEventHandler { | |||||||
|   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; |   virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class GAPScanEventHandler { | ||||||
|  |  public: | ||||||
|  |   virtual void gap_scan_event_handler(const BLEScanResult &scan_result) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class GATTcEventHandler { | class GATTcEventHandler { | ||||||
|  public: |  public: | ||||||
|   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
| @@ -101,6 +118,9 @@ class ESP32BLE : public Component { | |||||||
|   void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback); |   void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback); | ||||||
|  |  | ||||||
|   void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } |   void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } | ||||||
|  |   void register_gap_scan_event_handler(GAPScanEventHandler *handler) { | ||||||
|  |     this->gap_scan_event_handlers_.push_back(handler); | ||||||
|  |   } | ||||||
|   void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } |   void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } | ||||||
|   void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); } |   void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); } | ||||||
|   void register_ble_status_event_handler(BLEStatusEventHandler *handler) { |   void register_ble_status_event_handler(BLEStatusEventHandler *handler) { | ||||||
| @@ -113,22 +133,23 @@ class ESP32BLE : public Component { | |||||||
|   static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); |   static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); | ||||||
|   static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); |   static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); | ||||||
|  |  | ||||||
|   void real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); |  | ||||||
|   void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); |  | ||||||
|   void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); |  | ||||||
|  |  | ||||||
|   bool ble_setup_(); |   bool ble_setup_(); | ||||||
|   bool ble_dismantle_(); |   bool ble_dismantle_(); | ||||||
|   bool ble_pre_setup_(); |   bool ble_pre_setup_(); | ||||||
|   void advertising_init_(); |   void advertising_init_(); | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   template<typename... Args> friend void enqueue_ble_event(Args... args); | ||||||
|  |  | ||||||
|   std::vector<GAPEventHandler *> gap_event_handlers_; |   std::vector<GAPEventHandler *> gap_event_handlers_; | ||||||
|  |   std::vector<GAPScanEventHandler *> gap_scan_event_handlers_; | ||||||
|   std::vector<GATTcEventHandler *> gattc_event_handlers_; |   std::vector<GATTcEventHandler *> gattc_event_handlers_; | ||||||
|   std::vector<GATTsEventHandler *> gatts_event_handlers_; |   std::vector<GATTsEventHandler *> gatts_event_handlers_; | ||||||
|   std::vector<BLEStatusEventHandler *> ble_status_event_handlers_; |   std::vector<BLEStatusEventHandler *> ble_status_event_handlers_; | ||||||
|   BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; |   BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; | ||||||
|  |  | ||||||
|   Queue<BLEEvent> ble_events_; |   LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_; | ||||||
|  |   BLEEventPool<MAX_BLE_QUEUE_SIZE> ble_event_pool_; | ||||||
|   BLEAdvertising *advertising_{}; |   BLEAdvertising *advertising_{}; | ||||||
|   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; |   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; | ||||||
|   uint32_t advertising_cycle_time_{}; |   uint32_t advertising_cycle_time_{}; | ||||||
|   | |||||||
| @@ -2,92 +2,399 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <cstddef>  // for offsetof | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| #include <esp_gap_ble_api.h> | #include <esp_gap_ble_api.h> | ||||||
| #include <esp_gattc_api.h> | #include <esp_gattc_api.h> | ||||||
| #include <esp_gatts_api.h> | #include <esp_gatts_api.h> | ||||||
|  |  | ||||||
|  | #include "ble_scan_result.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_ble { | namespace esp32_ble { | ||||||
|  |  | ||||||
|  | // Compile-time verification that ESP-IDF scan complete events only contain a status field | ||||||
|  | // This ensures our reinterpret_cast in ble.cpp is safe | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF scan_param_cmpl structure has unexpected size"); | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF scan_start_cmpl structure has unexpected size"); | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF scan_stop_cmpl structure has unexpected size"); | ||||||
|  |  | ||||||
|  | // Verify the status field is at offset 0 (first member) | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, scan_param_cmpl.status) == 0, | ||||||
|  |               "status must be first member of scan_param_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, scan_start_cmpl.status) == 0, | ||||||
|  |               "status must be first member of scan_start_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, scan_stop_cmpl.status) == 0, | ||||||
|  |               "status must be first member of scan_stop_cmpl"); | ||||||
|  |  | ||||||
|  | // Compile-time verification for advertising complete events | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF adv_data_cmpl structure has unexpected size"); | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_scan_rsp_data_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF scan_rsp_data_cmpl structure has unexpected size"); | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_data_raw_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF adv_data_raw_cmpl structure has unexpected size"); | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_start_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF adv_start_cmpl structure has unexpected size"); | ||||||
|  | static_assert(sizeof(esp_ble_gap_cb_param_t::ble_adv_stop_cmpl_evt_param) == sizeof(esp_bt_status_t), | ||||||
|  |               "ESP-IDF adv_stop_cmpl structure has unexpected size"); | ||||||
|  |  | ||||||
|  | // Verify the status field is at offset 0 for advertising events | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_cmpl.status) == 0, | ||||||
|  |               "status must be first member of adv_data_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, scan_rsp_data_cmpl.status) == 0, | ||||||
|  |               "status must be first member of scan_rsp_data_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, adv_data_raw_cmpl.status) == 0, | ||||||
|  |               "status must be first member of adv_data_raw_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, adv_start_cmpl.status) == 0, | ||||||
|  |               "status must be first member of adv_start_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, adv_stop_cmpl.status) == 0, | ||||||
|  |               "status must be first member of adv_stop_cmpl"); | ||||||
|  |  | ||||||
|  | // Compile-time verification for RSSI complete event structure | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.status) == 0, | ||||||
|  |               "status must be first member of read_rssi_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(esp_bt_status_t), | ||||||
|  |               "rssi must immediately follow status in read_rssi_cmpl"); | ||||||
|  | static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t), | ||||||
|  |               "remote_addr must follow rssi in read_rssi_cmpl"); | ||||||
|  |  | ||||||
| // Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop(). | // Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop(). | ||||||
| // This class stores each event in a single type. | // This class stores each event with minimal memory usage. | ||||||
|  | // GAP events (99% of traffic) don't have the vector overhead. | ||||||
|  | // GATTC/GATTS events use heap allocation for their param and data. | ||||||
|  | // | ||||||
|  | // Event flow: | ||||||
|  | // 1. ESP-IDF BLE stack calls our static handlers in the BLE task context | ||||||
|  | // 2. The handlers create a BLEEvent instance, copying only the data we need | ||||||
|  | // 3. The event is pushed to a thread-safe queue | ||||||
|  | // 4. In the main loop(), events are popped from the queue and processed | ||||||
|  | // 5. The event destructor cleans up any external allocations | ||||||
|  | // | ||||||
|  | // Thread safety: | ||||||
|  | // - GAP events: We copy only the fields we need directly into the union | ||||||
|  | // - GATTC/GATTS events: We heap-allocate and copy the entire param struct, ensuring | ||||||
|  | //   the data remains valid even after the BLE callback returns. The original | ||||||
|  | //   param pointer from ESP-IDF is only valid during the callback. | ||||||
|  | // | ||||||
|  | // CRITICAL DESIGN NOTE: | ||||||
|  | // The heap allocations for GATTC/GATTS events are REQUIRED for memory safety. | ||||||
|  | // DO NOT attempt to optimize by removing these allocations or storing pointers | ||||||
|  | // to the original ESP-IDF data. The ESP-IDF callback data has a different lifetime | ||||||
|  | // than our event processing, and accessing it after the callback returns would | ||||||
|  | // result in use-after-free bugs and crashes. | ||||||
| class BLEEvent { | class BLEEvent { | ||||||
|  public: |  public: | ||||||
|   BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { |  | ||||||
|     this->event_.gap.gap_event = e; |  | ||||||
|     memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t)); |  | ||||||
|     this->type_ = GAP; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { |  | ||||||
|     this->event_.gattc.gattc_event = e; |  | ||||||
|     this->event_.gattc.gattc_if = i; |  | ||||||
|     memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t)); |  | ||||||
|     // Need to also make a copy of relevant event data. |  | ||||||
|     switch (e) { |  | ||||||
|       case ESP_GATTC_NOTIFY_EVT: |  | ||||||
|         this->data.assign(p->notify.value, p->notify.value + p->notify.value_len); |  | ||||||
|         this->event_.gattc.gattc_param.notify.value = this->data.data(); |  | ||||||
|         break; |  | ||||||
|       case ESP_GATTC_READ_CHAR_EVT: |  | ||||||
|       case ESP_GATTC_READ_DESCR_EVT: |  | ||||||
|         this->data.assign(p->read.value, p->read.value + p->read.value_len); |  | ||||||
|         this->event_.gattc.gattc_param.read.value = this->data.data(); |  | ||||||
|         break; |  | ||||||
|       default: |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     this->type_ = GATTC; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { |  | ||||||
|     this->event_.gatts.gatts_event = e; |  | ||||||
|     this->event_.gatts.gatts_if = i; |  | ||||||
|     memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t)); |  | ||||||
|     // Need to also make a copy of relevant event data. |  | ||||||
|     switch (e) { |  | ||||||
|       case ESP_GATTS_WRITE_EVT: |  | ||||||
|         this->data.assign(p->write.value, p->write.value + p->write.len); |  | ||||||
|         this->event_.gatts.gatts_param.write.value = this->data.data(); |  | ||||||
|         break; |  | ||||||
|       default: |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     this->type_ = GATTS; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   union { |  | ||||||
|     // NOLINTNEXTLINE(readability-identifier-naming) |  | ||||||
|     struct gap_event { |  | ||||||
|       esp_gap_ble_cb_event_t gap_event; |  | ||||||
|       esp_ble_gap_cb_param_t gap_param; |  | ||||||
|     } gap; |  | ||||||
|  |  | ||||||
|     // NOLINTNEXTLINE(readability-identifier-naming) |  | ||||||
|     struct gattc_event { |  | ||||||
|       esp_gattc_cb_event_t gattc_event; |  | ||||||
|       esp_gatt_if_t gattc_if; |  | ||||||
|       esp_ble_gattc_cb_param_t gattc_param; |  | ||||||
|     } gattc; |  | ||||||
|  |  | ||||||
|     // NOLINTNEXTLINE(readability-identifier-naming) |  | ||||||
|     struct gatts_event { |  | ||||||
|       esp_gatts_cb_event_t gatts_event; |  | ||||||
|       esp_gatt_if_t gatts_if; |  | ||||||
|       esp_ble_gatts_cb_param_t gatts_param; |  | ||||||
|     } gatts; |  | ||||||
|   } event_; |  | ||||||
|  |  | ||||||
|   std::vector<uint8_t> data{}; |  | ||||||
|   // NOLINTNEXTLINE(readability-identifier-naming) |   // NOLINTNEXTLINE(readability-identifier-naming) | ||||||
|   enum ble_event_t : uint8_t { |   enum ble_event_t : uint8_t { | ||||||
|     GAP, |     GAP, | ||||||
|     GATTC, |     GATTC, | ||||||
|     GATTS, |     GATTS, | ||||||
|   } type_; |   }; | ||||||
|  |  | ||||||
|  |   // Type definitions for cleaner method signatures | ||||||
|  |   struct StatusOnlyData { | ||||||
|  |     esp_bt_status_t status; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   struct RSSICompleteData { | ||||||
|  |     esp_bt_status_t status; | ||||||
|  |     int8_t rssi; | ||||||
|  |     esp_bd_addr_t remote_addr; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // Constructor for GAP events - no external allocations needed | ||||||
|  |   BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { | ||||||
|  |     this->type_ = GAP; | ||||||
|  |     this->init_gap_data_(e, p); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Constructor for GATTC events - uses heap allocation | ||||||
|  |   // IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization. | ||||||
|  |   // The param pointer from ESP-IDF is only valid during the callback execution. | ||||||
|  |   // Since BLE events are processed asynchronously in the main loop, we must create | ||||||
|  |   // our own copy to ensure the data remains valid until the event is processed. | ||||||
|  |   BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { | ||||||
|  |     this->type_ = GATTC; | ||||||
|  |     this->init_gattc_data_(e, i, p); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Constructor for GATTS events - uses heap allocation | ||||||
|  |   // IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization. | ||||||
|  |   // The param pointer from ESP-IDF is only valid during the callback execution. | ||||||
|  |   // Since BLE events are processed asynchronously in the main loop, we must create | ||||||
|  |   // our own copy to ensure the data remains valid until the event is processed. | ||||||
|  |   BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { | ||||||
|  |     this->type_ = GATTS; | ||||||
|  |     this->init_gatts_data_(e, i, p); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Destructor to clean up heap allocations | ||||||
|  |   ~BLEEvent() { this->cleanup_heap_data(); } | ||||||
|  |  | ||||||
|  |   // Default constructor for pre-allocation in pool | ||||||
|  |   BLEEvent() : type_(GAP) {} | ||||||
|  |  | ||||||
|  |   // Clean up any heap-allocated data | ||||||
|  |   void cleanup_heap_data() { | ||||||
|  |     if (this->type_ == GAP) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (this->type_ == GATTC) { | ||||||
|  |       delete this->event_.gattc.gattc_param; | ||||||
|  |       delete this->event_.gattc.data; | ||||||
|  |       this->event_.gattc.gattc_param = nullptr; | ||||||
|  |       this->event_.gattc.data = nullptr; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (this->type_ == GATTS) { | ||||||
|  |       delete this->event_.gatts.gatts_param; | ||||||
|  |       delete this->event_.gatts.data; | ||||||
|  |       this->event_.gatts.gatts_param = nullptr; | ||||||
|  |       this->event_.gatts.data = nullptr; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Load new event data for reuse (replaces previous event data) | ||||||
|  |   void load_gap_event(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { | ||||||
|  |     this->cleanup_heap_data(); | ||||||
|  |     this->type_ = GAP; | ||||||
|  |     this->init_gap_data_(e, p); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void load_gattc_event(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { | ||||||
|  |     this->cleanup_heap_data(); | ||||||
|  |     this->type_ = GATTC; | ||||||
|  |     this->init_gattc_data_(e, i, p); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void load_gatts_event(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { | ||||||
|  |     this->cleanup_heap_data(); | ||||||
|  |     this->type_ = GATTS; | ||||||
|  |     this->init_gatts_data_(e, i, p); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Disable copy to prevent double-delete | ||||||
|  |   BLEEvent(const BLEEvent &) = delete; | ||||||
|  |   BLEEvent &operator=(const BLEEvent &) = delete; | ||||||
|  |  | ||||||
|  |   union { | ||||||
|  |     // NOLINTNEXTLINE(readability-identifier-naming) | ||||||
|  |     struct gap_event { | ||||||
|  |       esp_gap_ble_cb_event_t gap_event; | ||||||
|  |       union { | ||||||
|  |         BLEScanResult scan_result;  // 73 bytes - Used by: esp32_ble_tracker | ||||||
|  |         // This matches ESP-IDF's scan complete event structures | ||||||
|  |         // All three (scan_param_cmpl, scan_start_cmpl, scan_stop_cmpl) have identical layout | ||||||
|  |         // Used by: esp32_ble_tracker | ||||||
|  |         StatusOnlyData scan_complete;  // 1 byte | ||||||
|  |         // Advertising complete events all have same structure | ||||||
|  |         // Used by: esp32_ble_beacon, esp32_ble server components | ||||||
|  |         // ADV_DATA_SET, SCAN_RSP_DATA_SET, ADV_DATA_RAW_SET, ADV_START, ADV_STOP | ||||||
|  |         StatusOnlyData adv_complete;  // 1 byte | ||||||
|  |         // RSSI complete event | ||||||
|  |         // Used by: ble_client (ble_rssi_sensor component) | ||||||
|  |         RSSICompleteData read_rssi_complete;  // 8 bytes | ||||||
|  |         // Security events - we store the full security union | ||||||
|  |         // Used by: ble_client (automation), bluetooth_proxy, esp32_ble_client | ||||||
|  |         esp_ble_sec_t security;  // Variable size, but fits within scan_result size | ||||||
|  |       }; | ||||||
|  |     } gap;  // 80 bytes total | ||||||
|  |  | ||||||
|  |     // NOLINTNEXTLINE(readability-identifier-naming) | ||||||
|  |     struct gattc_event { | ||||||
|  |       esp_gattc_cb_event_t gattc_event; | ||||||
|  |       esp_gatt_if_t gattc_if; | ||||||
|  |       esp_ble_gattc_cb_param_t *gattc_param;  // Heap-allocated | ||||||
|  |       std::vector<uint8_t> *data;             // Heap-allocated | ||||||
|  |     } gattc;                                  // 16 bytes (pointers only) | ||||||
|  |  | ||||||
|  |     // NOLINTNEXTLINE(readability-identifier-naming) | ||||||
|  |     struct gatts_event { | ||||||
|  |       esp_gatts_cb_event_t gatts_event; | ||||||
|  |       esp_gatt_if_t gatts_if; | ||||||
|  |       esp_ble_gatts_cb_param_t *gatts_param;  // Heap-allocated | ||||||
|  |       std::vector<uint8_t> *data;             // Heap-allocated | ||||||
|  |     } gatts;                                  // 16 bytes (pointers only) | ||||||
|  |   } event_;                                   // 80 bytes | ||||||
|  |  | ||||||
|  |   ble_event_t type_; | ||||||
|  |  | ||||||
|  |   // Helper methods to access event data | ||||||
|  |   ble_event_t type() const { return type_; } | ||||||
|  |   esp_gap_ble_cb_event_t gap_event_type() const { return event_.gap.gap_event; } | ||||||
|  |   const BLEScanResult &scan_result() const { return event_.gap.scan_result; } | ||||||
|  |   esp_bt_status_t scan_complete_status() const { return event_.gap.scan_complete.status; } | ||||||
|  |   esp_bt_status_t adv_complete_status() const { return event_.gap.adv_complete.status; } | ||||||
|  |   const RSSICompleteData &read_rssi_complete() const { return event_.gap.read_rssi_complete; } | ||||||
|  |   const esp_ble_sec_t &security() const { return event_.gap.security; } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   // Initialize GAP event data | ||||||
|  |   void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) { | ||||||
|  |     this->event_.gap.gap_event = e; | ||||||
|  |  | ||||||
|  |     if (p == nullptr) { | ||||||
|  |       return;  // Invalid event, but we can't log in header file | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Copy data based on event type | ||||||
|  |     switch (e) { | ||||||
|  |       case ESP_GAP_BLE_SCAN_RESULT_EVT: | ||||||
|  |         memcpy(this->event_.gap.scan_result.bda, p->scan_rst.bda, sizeof(esp_bd_addr_t)); | ||||||
|  |         this->event_.gap.scan_result.ble_addr_type = p->scan_rst.ble_addr_type; | ||||||
|  |         this->event_.gap.scan_result.rssi = p->scan_rst.rssi; | ||||||
|  |         this->event_.gap.scan_result.adv_data_len = p->scan_rst.adv_data_len; | ||||||
|  |         this->event_.gap.scan_result.scan_rsp_len = p->scan_rst.scan_rsp_len; | ||||||
|  |         this->event_.gap.scan_result.search_evt = p->scan_rst.search_evt; | ||||||
|  |         memcpy(this->event_.gap.scan_result.ble_adv, p->scan_rst.ble_adv, | ||||||
|  |                ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX); | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: | ||||||
|  |         this->event_.gap.scan_complete.status = p->scan_param_cmpl.status; | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: | ||||||
|  |         this->event_.gap.scan_complete.status = p->scan_start_cmpl.status; | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: | ||||||
|  |         this->event_.gap.scan_complete.status = p->scan_stop_cmpl.status; | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       // Advertising complete events - all have same structure with just status | ||||||
|  |       // Used by: esp32_ble_beacon, esp32_ble server components | ||||||
|  |       case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: | ||||||
|  |         this->event_.gap.adv_complete.status = p->adv_data_cmpl.status; | ||||||
|  |         break; | ||||||
|  |       case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: | ||||||
|  |         this->event_.gap.adv_complete.status = p->scan_rsp_data_cmpl.status; | ||||||
|  |         break; | ||||||
|  |       case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:  // Used by: esp32_ble_beacon | ||||||
|  |         this->event_.gap.adv_complete.status = p->adv_data_raw_cmpl.status; | ||||||
|  |         break; | ||||||
|  |       case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:  // Used by: esp32_ble_beacon | ||||||
|  |         this->event_.gap.adv_complete.status = p->adv_start_cmpl.status; | ||||||
|  |         break; | ||||||
|  |       case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:  // Used by: esp32_ble_beacon | ||||||
|  |         this->event_.gap.adv_complete.status = p->adv_stop_cmpl.status; | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       // RSSI complete event | ||||||
|  |       // Used by: ble_client (ble_rssi_sensor) | ||||||
|  |       case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT: | ||||||
|  |         this->event_.gap.read_rssi_complete.status = p->read_rssi_cmpl.status; | ||||||
|  |         this->event_.gap.read_rssi_complete.rssi = p->read_rssi_cmpl.rssi; | ||||||
|  |         memcpy(this->event_.gap.read_rssi_complete.remote_addr, p->read_rssi_cmpl.remote_addr, sizeof(esp_bd_addr_t)); | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       // Security events - copy the entire security union | ||||||
|  |       // Used by: ble_client, bluetooth_proxy, esp32_ble_client | ||||||
|  |       case ESP_GAP_BLE_AUTH_CMPL_EVT:      // Used by: bluetooth_proxy, esp32_ble_client | ||||||
|  |       case ESP_GAP_BLE_SEC_REQ_EVT:        // Used by: esp32_ble_client | ||||||
|  |       case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:  // Used by: ble_client automation | ||||||
|  |       case ESP_GAP_BLE_PASSKEY_REQ_EVT:    // Used by: ble_client automation | ||||||
|  |       case ESP_GAP_BLE_NC_REQ_EVT:         // Used by: ble_client automation | ||||||
|  |         memcpy(&this->event_.gap.security, &p->ble_security, sizeof(esp_ble_sec_t)); | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |       default: | ||||||
|  |         // We only store data for GAP events that components currently use | ||||||
|  |         // Unknown events still get queued and logged in ble.cpp:375 as | ||||||
|  |         // "Unhandled GAP event type in loop" - this helps identify new events | ||||||
|  |         // that components might need in the future | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Initialize GATTC event data | ||||||
|  |   void init_gattc_data_(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) { | ||||||
|  |     this->event_.gattc.gattc_event = e; | ||||||
|  |     this->event_.gattc.gattc_if = i; | ||||||
|  |  | ||||||
|  |     if (p == nullptr) { | ||||||
|  |       this->event_.gattc.gattc_param = nullptr; | ||||||
|  |       this->event_.gattc.data = nullptr; | ||||||
|  |       return;  // Invalid event, but we can't log in header file | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Heap-allocate param and data | ||||||
|  |     // Heap allocation is used because GATTC/GATTS events are rare (<1% of events) | ||||||
|  |     // while GAP events (99%) are stored inline to minimize memory usage | ||||||
|  |     // IMPORTANT: This heap allocation provides clear ownership semantics: | ||||||
|  |     // - The BLEEvent owns the allocated memory for its lifetime | ||||||
|  |     // - The data remains valid from the BLE callback context until processed in the main loop | ||||||
|  |     // - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory | ||||||
|  |     this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p); | ||||||
|  |  | ||||||
|  |     // Copy data for events that need it | ||||||
|  |     // The param struct contains pointers (e.g., notify.value) that point to temporary buffers. | ||||||
|  |     // We must copy this data to ensure it remains valid when the event is processed later. | ||||||
|  |     switch (e) { | ||||||
|  |       case ESP_GATTC_NOTIFY_EVT: | ||||||
|  |         this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len); | ||||||
|  |         this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data(); | ||||||
|  |         break; | ||||||
|  |       case ESP_GATTC_READ_CHAR_EVT: | ||||||
|  |       case ESP_GATTC_READ_DESCR_EVT: | ||||||
|  |         this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len); | ||||||
|  |         this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data(); | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         this->event_.gattc.data = nullptr; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Initialize GATTS event data | ||||||
|  |   void init_gatts_data_(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) { | ||||||
|  |     this->event_.gatts.gatts_event = e; | ||||||
|  |     this->event_.gatts.gatts_if = i; | ||||||
|  |  | ||||||
|  |     if (p == nullptr) { | ||||||
|  |       this->event_.gatts.gatts_param = nullptr; | ||||||
|  |       this->event_.gatts.data = nullptr; | ||||||
|  |       return;  // Invalid event, but we can't log in header file | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Heap-allocate param and data | ||||||
|  |     // Heap allocation is used because GATTC/GATTS events are rare (<1% of events) | ||||||
|  |     // while GAP events (99%) are stored inline to minimize memory usage | ||||||
|  |     // IMPORTANT: This heap allocation provides clear ownership semantics: | ||||||
|  |     // - The BLEEvent owns the allocated memory for its lifetime | ||||||
|  |     // - The data remains valid from the BLE callback context until processed in the main loop | ||||||
|  |     // - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory | ||||||
|  |     this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p); | ||||||
|  |  | ||||||
|  |     // Copy data for events that need it | ||||||
|  |     // The param struct contains pointers (e.g., write.value) that point to temporary buffers. | ||||||
|  |     // We must copy this data to ensure it remains valid when the event is processed later. | ||||||
|  |     switch (e) { | ||||||
|  |       case ESP_GATTS_WRITE_EVT: | ||||||
|  |         this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len); | ||||||
|  |         this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data(); | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         this->event_.gatts.data = nullptr; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // Verify the gap_event struct hasn't grown beyond expected size | ||||||
|  | // The gap member in the union should be 80 bytes (including the gap_event enum) | ||||||
|  | static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes"); | ||||||
|  |  | ||||||
|  | // Verify esp_ble_sec_t fits within our union | ||||||
|  | static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult"); | ||||||
|  |  | ||||||
|  | // BLEEvent total size: 84 bytes (80 byte union + 1 byte type + 3 bytes padding) | ||||||
|  |  | ||||||
| }  // namespace esp32_ble | }  // namespace esp32_ble | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								esphome/components/esp32_ble/ble_event_pool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								esphome/components/esp32_ble/ble_event_pool.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <cstddef> | ||||||
|  | #include "ble_event.h" | ||||||
|  | #include "queue.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace esp32_ble { | ||||||
|  |  | ||||||
|  | // BLE Event Pool - On-demand pool of BLEEvent objects to avoid heap fragmentation | ||||||
|  | // Events are allocated on first use and reused thereafter, growing to peak usage | ||||||
|  | template<uint8_t SIZE> class BLEEventPool { | ||||||
|  |  public: | ||||||
|  |   BLEEventPool() : total_created_(0) {} | ||||||
|  |  | ||||||
|  |   ~BLEEventPool() { | ||||||
|  |     // Clean up any remaining events in the free list | ||||||
|  |     BLEEvent *event; | ||||||
|  |     while ((event = this->free_list_.pop()) != nullptr) { | ||||||
|  |       delete event; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Allocate an event from the pool | ||||||
|  |   // Returns nullptr if pool is full | ||||||
|  |   BLEEvent *allocate() { | ||||||
|  |     // Try to get from free list first | ||||||
|  |     BLEEvent *event = this->free_list_.pop(); | ||||||
|  |     if (event != nullptr) | ||||||
|  |       return event; | ||||||
|  |  | ||||||
|  |     // Need to create a new event | ||||||
|  |     if (this->total_created_ >= SIZE) { | ||||||
|  |       // Pool is at capacity | ||||||
|  |       return nullptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Use internal RAM for better performance | ||||||
|  |     RAMAllocator<BLEEvent> allocator(RAMAllocator<BLEEvent>::ALLOC_INTERNAL); | ||||||
|  |     event = allocator.allocate(1); | ||||||
|  |  | ||||||
|  |     if (event == nullptr) { | ||||||
|  |       // Memory allocation failed | ||||||
|  |       return nullptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Placement new to construct the object | ||||||
|  |     new (event) BLEEvent(); | ||||||
|  |     this->total_created_++; | ||||||
|  |     return event; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Return an event to the pool for reuse | ||||||
|  |   void release(BLEEvent *event) { | ||||||
|  |     if (event != nullptr) { | ||||||
|  |       this->free_list_.push(event); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   LockFreeQueue<BLEEvent, SIZE> free_list_;  // Free events ready for reuse | ||||||
|  |   uint8_t total_created_;                    // Total events created (high water mark) | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace esp32_ble | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										24
									
								
								esphome/components/esp32_ble/ble_scan_result.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								esphome/components/esp32_ble/ble_scan_result.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <esp_gap_ble_api.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace esp32_ble { | ||||||
|  |  | ||||||
|  | // Structure for BLE scan results - only fields we actually use | ||||||
|  | struct __attribute__((packed)) BLEScanResult { | ||||||
|  |   esp_bd_addr_t bda; | ||||||
|  |   uint8_t ble_addr_type; | ||||||
|  |   int8_t rssi; | ||||||
|  |   uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX]; | ||||||
|  |   uint8_t adv_data_len; | ||||||
|  |   uint8_t scan_rsp_len; | ||||||
|  |   uint8_t search_evt; | ||||||
|  | };  // ~73 bytes vs ~400 bytes for full esp_ble_gap_cb_param_t | ||||||
|  |  | ||||||
|  | }  // namespace esp32_ble | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -2,52 +2,81 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include <mutex> | #include <atomic> | ||||||
| #include <queue> | #include <cstddef> | ||||||
|  |  | ||||||
| #include <freertos/FreeRTOS.h> |  | ||||||
| #include <freertos/semphr.h> |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather |  * BLE events come in from a separate Task (thread) in the ESP32 stack. Rather | ||||||
|  * than trying to deal with various locking strategies, all incoming GAP and GATT |  * than using mutex-based locking, this lock-free queue allows the BLE | ||||||
|  * events will simply be placed on a semaphore guarded queue. The next time the |  * task to enqueue events without blocking. The main loop() then processes | ||||||
|  * component runs loop(), these events are popped off the queue and handed at |  * these events at a safer time. | ||||||
|  * this safer time. |  * | ||||||
|  |  * This is a Single-Producer Single-Consumer (SPSC) lock-free ring buffer. | ||||||
|  |  * The BLE task is the only producer, and the main loop() is the only consumer. | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_ble { | namespace esp32_ble { | ||||||
|  |  | ||||||
| template<class T> class Queue { | template<class T, uint8_t SIZE> class LockFreeQueue { | ||||||
|  public: |  public: | ||||||
|   Queue() { m_ = xSemaphoreCreateMutex(); } |   LockFreeQueue() : head_(0), tail_(0), dropped_count_(0) {} | ||||||
|  |  | ||||||
|   void push(T *element) { |   bool push(T *element) { | ||||||
|     if (element == nullptr) |     if (element == nullptr) | ||||||
|       return; |       return false; | ||||||
|     // It is not called from main loop. Thus it won't block main thread. |  | ||||||
|     xSemaphoreTake(m_, portMAX_DELAY); |     uint8_t current_tail = tail_.load(std::memory_order_relaxed); | ||||||
|     q_.push(element); |     uint8_t next_tail = (current_tail + 1) % SIZE; | ||||||
|     xSemaphoreGive(m_); |  | ||||||
|  |     if (next_tail == head_.load(std::memory_order_acquire)) { | ||||||
|  |       // Buffer full | ||||||
|  |       dropped_count_.fetch_add(1, std::memory_order_relaxed); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buffer_[current_tail] = element; | ||||||
|  |     tail_.store(next_tail, std::memory_order_release); | ||||||
|  |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   T *pop() { |   T *pop() { | ||||||
|     T *element = nullptr; |     uint8_t current_head = head_.load(std::memory_order_relaxed); | ||||||
|  |  | ||||||
|     if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) { |     if (current_head == tail_.load(std::memory_order_acquire)) { | ||||||
|       if (!q_.empty()) { |       return nullptr;  // Empty | ||||||
|         element = q_.front(); |  | ||||||
|         q_.pop(); |  | ||||||
|       } |  | ||||||
|       xSemaphoreGive(m_); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     T *element = buffer_[current_head]; | ||||||
|  |     head_.store((current_head + 1) % SIZE, std::memory_order_release); | ||||||
|     return element; |     return element; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   size_t size() const { | ||||||
|  |     uint8_t tail = tail_.load(std::memory_order_acquire); | ||||||
|  |     uint8_t head = head_.load(std::memory_order_acquire); | ||||||
|  |     return (tail - head + SIZE) % SIZE; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint16_t get_and_reset_dropped_count() { return dropped_count_.exchange(0, std::memory_order_relaxed); } | ||||||
|  |  | ||||||
|  |   void increment_dropped_count() { dropped_count_.fetch_add(1, std::memory_order_relaxed); } | ||||||
|  |  | ||||||
|  |   bool empty() const { return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire); } | ||||||
|  |  | ||||||
|  |   bool full() const { | ||||||
|  |     uint8_t next_tail = (tail_.load(std::memory_order_relaxed) + 1) % SIZE; | ||||||
|  |     return next_tail == head_.load(std::memory_order_acquire); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::queue<T *> q_; |   T *buffer_[SIZE]; | ||||||
|   SemaphoreHandle_t m_; |   // Atomic: written by producer (push/increment), read+reset by consumer (get_and_reset) | ||||||
|  |   std::atomic<uint16_t> dropped_count_;  // 65535 max - more than enough for drop tracking | ||||||
|  |   // Atomic: written by consumer (pop), read by producer (push) to check if full | ||||||
|  |   std::atomic<uint8_t> head_; | ||||||
|  |   // Atomic: written by producer (push), read by consumer (pop) to check if empty | ||||||
|  |   std::atomic<uint8_t> tail_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace esp32_ble | }  // namespace esp32_ble | ||||||
|   | |||||||
| @@ -22,6 +22,16 @@ void BLEClientBase::setup() { | |||||||
|   this->connection_index_ = connection_index++; |   this->connection_index_ = connection_index++; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void BLEClientBase::set_state(espbt::ClientState st) { | ||||||
|  |   ESP_LOGV(TAG, "[%d] [%s] Set state %d", this->connection_index_, this->address_str_.c_str(), (int) st); | ||||||
|  |   ESPBTClient::set_state(st); | ||||||
|  |  | ||||||
|  |   if (st == espbt::ClientState::READY_TO_CONNECT) { | ||||||
|  |     // Enable loop when we need to connect | ||||||
|  |     this->enable_loop(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| void BLEClientBase::loop() { | void BLEClientBase::loop() { | ||||||
|   if (!esp32_ble::global_ble->is_active()) { |   if (!esp32_ble::global_ble->is_active()) { | ||||||
|     this->set_state(espbt::ClientState::INIT); |     this->set_state(espbt::ClientState::INIT); | ||||||
| @@ -37,9 +47,14 @@ void BLEClientBase::loop() { | |||||||
|   } |   } | ||||||
|   // READY_TO_CONNECT means we have discovered the device |   // READY_TO_CONNECT means we have discovered the device | ||||||
|   // and the scanner has been stopped by the tracker. |   // and the scanner has been stopped by the tracker. | ||||||
|   if (this->state_ == espbt::ClientState::READY_TO_CONNECT) { |   else if (this->state_ == espbt::ClientState::READY_TO_CONNECT) { | ||||||
|     this->connect(); |     this->connect(); | ||||||
|   } |   } | ||||||
|  |   // If its idle, we can disable the loop as set_state | ||||||
|  |   // will enable it again when we need to connect. | ||||||
|  |   else if (this->state_ == espbt::ClientState::IDLE) { | ||||||
|  |     this->disable_loop(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | ||||||
|   | |||||||
| @@ -93,22 +93,37 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | |||||||
|  |  | ||||||
|   bool check_addr(esp_bd_addr_t &addr) { return memcmp(addr, this->remote_bda_, sizeof(esp_bd_addr_t)) == 0; } |   bool check_addr(esp_bd_addr_t &addr) { return memcmp(addr, this->remote_bda_, sizeof(esp_bd_addr_t)) == 0; } | ||||||
|  |  | ||||||
|  |   void set_state(espbt::ClientState st) override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   int gattc_if_; |   // Memory optimized layout for 32-bit systems | ||||||
|   esp_bd_addr_t remote_bda_; |   // Group 1: 8-byte types | ||||||
|   esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC}; |  | ||||||
|   uint16_t conn_id_{UNSET_CONN_ID}; |  | ||||||
|   uint64_t address_{0}; |   uint64_t address_{0}; | ||||||
|   bool auto_connect_{false}; |  | ||||||
|  |   // Group 2: Container types (grouped for memory optimization) | ||||||
|   std::string address_str_{}; |   std::string address_str_{}; | ||||||
|   uint8_t connection_index_; |  | ||||||
|   int16_t service_count_{0}; |  | ||||||
|   uint16_t mtu_{23}; |  | ||||||
|   bool paired_{false}; |  | ||||||
|   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; |  | ||||||
|   std::vector<BLEService *> services_; |   std::vector<BLEService *> services_; | ||||||
|  |  | ||||||
|  |   // Group 3: 4-byte types | ||||||
|  |   int gattc_if_; | ||||||
|   esp_gatt_status_t status_{ESP_GATT_OK}; |   esp_gatt_status_t status_{ESP_GATT_OK}; | ||||||
|  |  | ||||||
|  |   // Group 4: Arrays (6 bytes) | ||||||
|  |   esp_bd_addr_t remote_bda_; | ||||||
|  |  | ||||||
|  |   // Group 5: 2-byte types | ||||||
|  |   uint16_t conn_id_{UNSET_CONN_ID}; | ||||||
|  |   uint16_t mtu_{23}; | ||||||
|  |  | ||||||
|  |   // Group 6: 1-byte types and small enums | ||||||
|  |   esp_ble_addr_type_t remote_addr_type_{BLE_ADDR_TYPE_PUBLIC}; | ||||||
|  |   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; | ||||||
|  |   uint8_t connection_index_; | ||||||
|  |   uint8_t service_count_{0};  // ESP32 has max handles < 255, typical devices have < 50 services | ||||||
|  |   bool auto_connect_{false}; | ||||||
|  |   bool paired_{false}; | ||||||
|  |   // 6 bytes used, 2 bytes padding | ||||||
|  |  | ||||||
|   void log_event_(const char *name); |   void log_event_(const char *name); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -268,6 +268,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) |     parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) | ||||||
|     cg.add(parent.register_gap_event_handler(var)) |     cg.add(parent.register_gap_event_handler(var)) | ||||||
|  |     cg.add(parent.register_gap_scan_event_handler(var)) | ||||||
|     cg.add(parent.register_gattc_event_handler(var)) |     cg.add(parent.register_gattc_event_handler(var)) | ||||||
|     cg.add(parent.register_ble_status_event_handler(var)) |     cg.add(parent.register_ble_status_event_handler(var)) | ||||||
|     cg.add(var.set_parent(parent)) |     cg.add(var.set_parent(parent)) | ||||||
|   | |||||||
| @@ -50,17 +50,15 @@ void ESP32BLETracker::setup() { | |||||||
|     ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE"); |     ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param> allocator( |   RAMAllocator<BLEScanResult> allocator; | ||||||
|       ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param>::ALLOW_FAILURE); |   this->scan_ring_buffer_ = allocator.allocate(SCAN_RESULT_BUFFER_SIZE); | ||||||
|   this->scan_result_buffer_ = allocator.allocate(ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE); |  | ||||||
|  |  | ||||||
|   if (this->scan_result_buffer_ == nullptr) { |   if (this->scan_ring_buffer_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Could not allocate buffer for BLE Tracker!"); |     ESP_LOGE(TAG, "Could not allocate ring buffer for BLE Tracker!"); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   global_esp32_ble_tracker = this; |   global_esp32_ble_tracker = this; | ||||||
|   this->scan_result_lock_ = xSemaphoreCreateMutex(); |  | ||||||
|  |  | ||||||
| #ifdef USE_OTA | #ifdef USE_OTA | ||||||
|   ota::get_global_ota_callback()->add_on_state_callback( |   ota::get_global_ota_callback()->add_on_state_callback( | ||||||
| @@ -120,27 +118,31 @@ void ESP32BLETracker::loop() { | |||||||
|   } |   } | ||||||
|   bool promote_to_connecting = discovered && !searching && !connecting; |   bool promote_to_connecting = discovered && !searching && !connecting; | ||||||
|  |  | ||||||
|   if (this->scanner_state_ == ScannerState::RUNNING && |   // Process scan results from lock-free SPSC ring buffer | ||||||
|       this->scan_result_index_ &&  // if it looks like we have a scan result we will take the lock |   // Consumer side: This runs in the main loop thread | ||||||
|       xSemaphoreTake(this->scan_result_lock_, 0)) { |   if (this->scanner_state_ == ScannerState::RUNNING) { | ||||||
|     uint32_t index = this->scan_result_index_; |     // Load our own index with relaxed ordering (we're the only writer) | ||||||
|     if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { |     uint8_t read_idx = this->ring_read_index_.load(std::memory_order_relaxed); | ||||||
|       ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (this->raw_advertisements_) { |     // Load producer's index with acquire to see their latest writes | ||||||
|       for (auto *listener : this->listeners_) { |     uint8_t write_idx = this->ring_write_index_.load(std::memory_order_acquire); | ||||||
|         listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_); |  | ||||||
|       } |  | ||||||
|       for (auto *client : this->clients_) { |  | ||||||
|         client->parse_devices(this->scan_result_buffer_, this->scan_result_index_); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (this->parse_advertisements_) { |     while (read_idx != write_idx) { | ||||||
|       for (size_t i = 0; i < index; i++) { |       // Process one result at a time directly from ring buffer | ||||||
|  |       BLEScanResult &scan_result = this->scan_ring_buffer_[read_idx]; | ||||||
|  |  | ||||||
|  |       if (this->raw_advertisements_) { | ||||||
|  |         for (auto *listener : this->listeners_) { | ||||||
|  |           listener->parse_devices(&scan_result, 1); | ||||||
|  |         } | ||||||
|  |         for (auto *client : this->clients_) { | ||||||
|  |           client->parse_devices(&scan_result, 1); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (this->parse_advertisements_) { | ||||||
|         ESPBTDevice device; |         ESPBTDevice device; | ||||||
|         device.parse_scan_rst(this->scan_result_buffer_[i]); |         device.parse_scan_rst(scan_result); | ||||||
|  |  | ||||||
|         bool found = false; |         bool found = false; | ||||||
|         for (auto *listener : this->listeners_) { |         for (auto *listener : this->listeners_) { | ||||||
| @@ -161,9 +163,19 @@ void ESP32BLETracker::loop() { | |||||||
|           this->print_bt_device_info(device); |           this->print_bt_device_info(device); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Move to next entry in ring buffer | ||||||
|  |       read_idx = (read_idx + 1) % SCAN_RESULT_BUFFER_SIZE; | ||||||
|  |  | ||||||
|  |       // Store with release to ensure reads complete before index update | ||||||
|  |       this->ring_read_index_.store(read_idx, std::memory_order_release); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Log dropped results periodically | ||||||
|  |     size_t dropped = this->scan_results_dropped_.exchange(0, std::memory_order_relaxed); | ||||||
|  |     if (dropped > 0) { | ||||||
|  |       ESP_LOGW(TAG, "Dropped %zu BLE scan results due to buffer overflow", dropped); | ||||||
|     } |     } | ||||||
|     this->scan_result_index_ = 0; |  | ||||||
|     xSemaphoreGive(this->scan_result_lock_); |  | ||||||
|   } |   } | ||||||
|   if (this->scanner_state_ == ScannerState::STOPPED) { |   if (this->scanner_state_ == ScannerState::STOPPED) { | ||||||
|     this->end_of_scan_();  // Change state to IDLE |     this->end_of_scan_();  // Change state to IDLE | ||||||
| @@ -370,9 +382,6 @@ void ESP32BLETracker::recalculate_advertisement_parser_types() { | |||||||
|  |  | ||||||
| void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     case ESP_GAP_BLE_SCAN_RESULT_EVT: |  | ||||||
|       this->gap_scan_result_(param->scan_rst); |  | ||||||
|       break; |  | ||||||
|     case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: |     case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: | ||||||
|       this->gap_scan_set_param_complete_(param->scan_param_cmpl); |       this->gap_scan_set_param_complete_(param->scan_param_cmpl); | ||||||
|       break; |       break; | ||||||
| @@ -385,11 +394,57 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga | |||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|  |   // Forward all events to clients (scan results are handled separately via gap_scan_event_handler) | ||||||
|   for (auto *client : this->clients_) { |   for (auto *client : this->clients_) { | ||||||
|     client->gap_event_handler(event, param); |     client->gap_event_handler(event, param); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) { | ||||||
|  |   ESP_LOGV(TAG, "gap_scan_result - event %d", scan_result.search_evt); | ||||||
|  |  | ||||||
|  |   if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { | ||||||
|  |     // Lock-free SPSC ring buffer write (Producer side) | ||||||
|  |     // This runs in the ESP-IDF Bluetooth stack callback thread | ||||||
|  |     // IMPORTANT: Only this thread writes to ring_write_index_ | ||||||
|  |  | ||||||
|  |     // Load our own index with relaxed ordering (we're the only writer) | ||||||
|  |     uint8_t write_idx = this->ring_write_index_.load(std::memory_order_relaxed); | ||||||
|  |     uint8_t next_write_idx = (write_idx + 1) % SCAN_RESULT_BUFFER_SIZE; | ||||||
|  |  | ||||||
|  |     // Load consumer's index with acquire to see their latest updates | ||||||
|  |     uint8_t read_idx = this->ring_read_index_.load(std::memory_order_acquire); | ||||||
|  |  | ||||||
|  |     // Check if buffer is full | ||||||
|  |     if (next_write_idx != read_idx) { | ||||||
|  |       // Write to ring buffer | ||||||
|  |       this->scan_ring_buffer_[write_idx] = scan_result; | ||||||
|  |  | ||||||
|  |       // Store with release to ensure the write is visible before index update | ||||||
|  |       this->ring_write_index_.store(next_write_idx, std::memory_order_release); | ||||||
|  |     } else { | ||||||
|  |       // Buffer full, track dropped results | ||||||
|  |       this->scan_results_dropped_.fetch_add(1, std::memory_order_relaxed); | ||||||
|  |     } | ||||||
|  |   } else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) { | ||||||
|  |     // Scan finished on its own | ||||||
|  |     if (this->scanner_state_ != ScannerState::RUNNING) { | ||||||
|  |       if (this->scanner_state_ == ScannerState::STOPPING) { | ||||||
|  |         ESP_LOGE(TAG, "Scan was not running when scan completed."); | ||||||
|  |       } else if (this->scanner_state_ == ScannerState::STARTING) { | ||||||
|  |         ESP_LOGE(TAG, "Scan was not started when scan completed."); | ||||||
|  |       } else if (this->scanner_state_ == ScannerState::FAILED) { | ||||||
|  |         ESP_LOGE(TAG, "Scan was in failed state when scan completed."); | ||||||
|  |       } else if (this->scanner_state_ == ScannerState::IDLE) { | ||||||
|  |         ESP_LOGE(TAG, "Scan was idle when scan completed."); | ||||||
|  |       } else if (this->scanner_state_ == ScannerState::STOPPED) { | ||||||
|  |         ESP_LOGE(TAG, "Scan was stopped when scan completed."); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     this->set_scanner_state_(ScannerState::STOPPED); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { | void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) { | ||||||
|   ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status); |   ESP_LOGV(TAG, "gap_scan_set_param_complete - status %d", param.status); | ||||||
|   if (param.status == ESP_BT_STATUS_DONE) { |   if (param.status == ESP_BT_STATUS_DONE) { | ||||||
| @@ -444,34 +499,6 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_ | |||||||
|   this->set_scanner_state_(ScannerState::STOPPED); |   this->set_scanner_state_(ScannerState::STOPPED); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { |  | ||||||
|   ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt); |  | ||||||
|   if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { |  | ||||||
|     if (xSemaphoreTake(this->scan_result_lock_, 0)) { |  | ||||||
|       if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { |  | ||||||
|         this->scan_result_buffer_[this->scan_result_index_++] = param; |  | ||||||
|       } |  | ||||||
|       xSemaphoreGive(this->scan_result_lock_); |  | ||||||
|     } |  | ||||||
|   } else if (param.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) { |  | ||||||
|     // Scan finished on its own |  | ||||||
|     if (this->scanner_state_ != ScannerState::RUNNING) { |  | ||||||
|       if (this->scanner_state_ == ScannerState::STOPPING) { |  | ||||||
|         ESP_LOGE(TAG, "Scan was not running when scan completed."); |  | ||||||
|       } else if (this->scanner_state_ == ScannerState::STARTING) { |  | ||||||
|         ESP_LOGE(TAG, "Scan was not started when scan completed."); |  | ||||||
|       } else if (this->scanner_state_ == ScannerState::FAILED) { |  | ||||||
|         ESP_LOGE(TAG, "Scan was in failed state when scan completed."); |  | ||||||
|       } else if (this->scanner_state_ == ScannerState::IDLE) { |  | ||||||
|         ESP_LOGE(TAG, "Scan was idle when scan completed."); |  | ||||||
|       } else if (this->scanner_state_ == ScannerState::STOPPED) { |  | ||||||
|         ESP_LOGE(TAG, "Scan was stopped when scan completed."); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     this->set_scanner_state_(ScannerState::STOPPED); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                                           esp_ble_gattc_cb_param_t *param) { |                                           esp_ble_gattc_cb_param_t *param) { | ||||||
|   for (auto *client : this->clients_) { |   for (auto *client : this->clients_) { | ||||||
| @@ -494,13 +521,15 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData | |||||||
|   return ESPBLEiBeacon(data.data.data()); |   return ESPBLEiBeacon(data.data.data()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { | void ESPBTDevice::parse_scan_rst(const BLEScanResult &scan_result) { | ||||||
|   this->scan_result_ = param; |  | ||||||
|   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] = param.bda[i]; |     this->address_[i] = scan_result.bda[i]; | ||||||
|   this->address_type_ = param.ble_addr_type; |   this->address_type_ = static_cast<esp_ble_addr_type_t>(scan_result.ble_addr_type); | ||||||
|   this->rssi_ = param.rssi; |   this->rssi_ = scan_result.rssi; | ||||||
|   this->parse_adv_(param); |  | ||||||
|  |   // Parse advertisement data directly | ||||||
|  |   uint8_t total_len = scan_result.adv_data_len + scan_result.scan_rsp_len; | ||||||
|  |   this->parse_adv_(scan_result.ble_adv, total_len); | ||||||
|  |  | ||||||
| #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE | #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE | ||||||
|   ESP_LOGVV(TAG, "Parse Result:"); |   ESP_LOGVV(TAG, "Parse Result:"); | ||||||
| @@ -558,13 +587,13 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e | |||||||
|     ESP_LOGVV(TAG, "    Data: %s", format_hex_pretty(data.data).c_str()); |     ESP_LOGVV(TAG, "    Data: %s", format_hex_pretty(data.data).c_str()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ESP_LOGVV(TAG, "  Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); |   ESP_LOGVV(TAG, "  Adv data: %s", | ||||||
|  |             format_hex_pretty(scan_result.ble_adv, scan_result.adv_data_len + scan_result.scan_rsp_len).c_str()); | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { |  | ||||||
|  | void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) { | ||||||
|   size_t offset = 0; |   size_t offset = 0; | ||||||
|   const uint8_t *payload = param.ble_adv; |  | ||||||
|   uint8_t len = param.adv_data_len + param.scan_rsp_len; |  | ||||||
|  |  | ||||||
|   while (offset + 2 < len) { |   while (offset + 2 < len) { | ||||||
|     const uint8_t field_length = payload[offset++];  // First byte is length of adv record |     const uint8_t field_length = payload[offset++];  // First byte is length of adv record | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #include <array> | #include <array> | ||||||
|  | #include <atomic> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| @@ -62,7 +63,7 @@ class ESPBLEiBeacon { | |||||||
|  |  | ||||||
| class ESPBTDevice { | class ESPBTDevice { | ||||||
|  public: |  public: | ||||||
|   void parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m); |   void parse_scan_rst(const BLEScanResult &scan_result); | ||||||
|  |  | ||||||
|   std::string address_str() const; |   std::string address_str() const; | ||||||
|  |  | ||||||
| @@ -84,8 +85,6 @@ class ESPBTDevice { | |||||||
|  |  | ||||||
|   const std::vector<ServiceData> &get_service_datas() const { return service_datas_; } |   const std::vector<ServiceData> &get_service_datas() const { return service_datas_; } | ||||||
|  |  | ||||||
|   const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &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 { | ||||||
| @@ -98,7 +97,7 @@ class ESPBTDevice { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m); |   void parse_adv_(const uint8_t *payload, uint8_t len); | ||||||
|  |  | ||||||
|   esp_bd_addr_t address_{ |   esp_bd_addr_t address_{ | ||||||
|       0, |       0, | ||||||
| @@ -112,7 +111,6 @@ 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_{}; | ||||||
|   esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ESP32BLETracker; | class ESP32BLETracker; | ||||||
| @@ -121,9 +119,7 @@ class ESPBTDeviceListener { | |||||||
|  public: |  public: | ||||||
|   virtual void on_scan_end() {} |   virtual void on_scan_end() {} | ||||||
|   virtual bool parse_device(const ESPBTDevice &device) = 0; |   virtual bool parse_device(const ESPBTDevice &device) = 0; | ||||||
|   virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) { |   virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; }; | ||||||
|     return false; |  | ||||||
|   }; |  | ||||||
|   virtual AdvertisementParserType get_advertisement_parser_type() { |   virtual AdvertisementParserType get_advertisement_parser_type() { | ||||||
|     return AdvertisementParserType::PARSED_ADVERTISEMENTS; |     return AdvertisementParserType::PARSED_ADVERTISEMENTS; | ||||||
|   }; |   }; | ||||||
| @@ -133,7 +129,7 @@ class ESPBTDeviceListener { | |||||||
|   ESP32BLETracker *parent_{nullptr}; |   ESP32BLETracker *parent_{nullptr}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class ClientState { | enum class ClientState : uint8_t { | ||||||
|   // Connection is allocated |   // Connection is allocated | ||||||
|   INIT, |   INIT, | ||||||
|   // Client is disconnecting |   // Client is disconnecting | ||||||
| @@ -169,7 +165,7 @@ enum class ScannerState { | |||||||
|   STOPPED, |   STOPPED, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class ConnectionType { | enum class ConnectionType : uint8_t { | ||||||
|   // The default connection type, we hold all the services in ram |   // The default connection type, we hold all the services in ram | ||||||
|   // for the duration of the connection. |   // for the duration of the connection. | ||||||
|   V1, |   V1, | ||||||
| @@ -197,19 +193,24 @@ class ESPBTClient : public ESPBTDeviceListener { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   ClientState state() const { return state_; } |   ClientState state() const { return state_; } | ||||||
|   int app_id; |  | ||||||
|  |   // Memory optimized layout | ||||||
|  |   uint8_t app_id;  // App IDs are small integers assigned sequentially | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   // Group 1: 1-byte types | ||||||
|   ClientState state_{ClientState::INIT}; |   ClientState state_{ClientState::INIT}; | ||||||
|   // want_disconnect_ is set to true when a disconnect is requested |   // want_disconnect_ is set to true when a disconnect is requested | ||||||
|   // while the client is connecting. This is used to disconnect the |   // while the client is connecting. This is used to disconnect the | ||||||
|   // client as soon as we get the connection id (conn_id_) from the |   // client as soon as we get the connection id (conn_id_) from the | ||||||
|   // ESP_GATTC_OPEN_EVT event. |   // ESP_GATTC_OPEN_EVT event. | ||||||
|   bool want_disconnect_{false}; |   bool want_disconnect_{false}; | ||||||
|  |   // 2 bytes used, 2 bytes padding | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ESP32BLETracker : public Component, | class ESP32BLETracker : public Component, | ||||||
|                         public GAPEventHandler, |                         public GAPEventHandler, | ||||||
|  |                         public GAPScanEventHandler, | ||||||
|                         public GATTcEventHandler, |                         public GATTcEventHandler, | ||||||
|                         public BLEStatusEventHandler, |                         public BLEStatusEventHandler, | ||||||
|                         public Parented<ESP32BLE> { |                         public Parented<ESP32BLE> { | ||||||
| @@ -240,6 +241,7 @@ class ESP32BLETracker : public Component, | |||||||
|   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                            esp_ble_gattc_cb_param_t *param) override; |                            esp_ble_gattc_cb_param_t *param) override; | ||||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; |   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||||
|  |   void gap_scan_event_handler(const BLEScanResult &scan_result) override; | ||||||
|   void ble_before_disabled_event_handler() override; |   void ble_before_disabled_event_handler() override; | ||||||
|  |  | ||||||
|   void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) { |   void add_scanner_state_callback(std::function<void(ScannerState)> &&callback) { | ||||||
| @@ -264,7 +266,7 @@ class ESP32BLETracker : public Component, | |||||||
|   /// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed. |   /// Called to set the scanner state. Will also call callbacks to let listeners know when state is changed. | ||||||
|   void set_scanner_state_(ScannerState state); |   void set_scanner_state_(ScannerState state); | ||||||
|  |  | ||||||
|   int app_id_{0}; |   uint8_t app_id_{0}; | ||||||
|  |  | ||||||
|   /// Vector of addresses that have already been printed in print_bt_device_info |   /// Vector of addresses that have already been printed in print_bt_device_info | ||||||
|   std::vector<uint64_t> already_discovered_; |   std::vector<uint64_t> already_discovered_; | ||||||
| @@ -285,14 +287,16 @@ class ESP32BLETracker : public Component, | |||||||
|   bool ble_was_disabled_{true}; |   bool ble_was_disabled_{true}; | ||||||
|   bool raw_advertisements_{false}; |   bool raw_advertisements_{false}; | ||||||
|   bool parse_advertisements_{false}; |   bool parse_advertisements_{false}; | ||||||
|   SemaphoreHandle_t scan_result_lock_; |  | ||||||
|   size_t scan_result_index_{0}; |   // Lock-free Single-Producer Single-Consumer (SPSC) ring buffer for scan results | ||||||
| #ifdef USE_PSRAM |   // Producer: ESP-IDF Bluetooth stack callback (gap_scan_event_handler) | ||||||
|   const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 32; |   // Consumer: ESPHome main loop (loop() method) | ||||||
| #else |   // This design ensures zero blocking in the BT callback and prevents scan result loss | ||||||
|   const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 20; |   BLEScanResult *scan_ring_buffer_; | ||||||
| #endif  // USE_PSRAM |   std::atomic<uint8_t> ring_write_index_{0};       // Written only by BT callback (producer) | ||||||
|   esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; |   std::atomic<uint8_t> ring_read_index_{0};        // Written only by main loop (consumer) | ||||||
|  |   std::atomic<uint16_t> scan_results_dropped_{0};  // Tracks buffer overflow events | ||||||
|  |  | ||||||
|   esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; |   esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; | ||||||
|   esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; |   esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; | ||||||
|   int connecting_{0}; |   int connecting_{0}; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| from esphome import automation, pins | from esphome import automation, pins | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c | ||||||
| from esphome.components.esp32 import add_idf_component | from esphome.components.esp32 import add_idf_component | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
| @@ -7,6 +8,7 @@ from esphome.const import ( | |||||||
|     CONF_CONTRAST, |     CONF_CONTRAST, | ||||||
|     CONF_DATA_PINS, |     CONF_DATA_PINS, | ||||||
|     CONF_FREQUENCY, |     CONF_FREQUENCY, | ||||||
|  |     CONF_I2C_ID, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
|     CONF_RESET_PIN, |     CONF_RESET_PIN, | ||||||
| @@ -149,93 +151,104 @@ CONF_ON_IMAGE = "on_image" | |||||||
|  |  | ||||||
| camera_range_param = cv.int_range(min=-2, max=2) | camera_range_param = cv.int_range(min=-2, max=2) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( | CONFIG_SCHEMA = cv.All( | ||||||
|     { |     cv.ENTITY_BASE_SCHEMA.extend( | ||||||
|         cv.GenerateID(): cv.declare_id(ESP32Camera), |         { | ||||||
|         # pin assignment |             cv.GenerateID(): cv.declare_id(ESP32Camera), | ||||||
|         cv.Required(CONF_DATA_PINS): cv.All( |             # pin assignment | ||||||
|             [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) |             cv.Required(CONF_DATA_PINS): cv.All( | ||||||
|         ), |                 [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) | ||||||
|         cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_input_pin_number, |             ), | ||||||
|         cv.Required(CONF_HREF_PIN): pins.internal_gpio_input_pin_number, |             cv.Required(CONF_VSYNC_PIN): pins.internal_gpio_input_pin_number, | ||||||
|         cv.Required(CONF_PIXEL_CLOCK_PIN): pins.internal_gpio_input_pin_number, |             cv.Required(CONF_HREF_PIN): pins.internal_gpio_input_pin_number, | ||||||
|         cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( |             cv.Required(CONF_PIXEL_CLOCK_PIN): pins.internal_gpio_input_pin_number, | ||||||
|             { |             cv.Required(CONF_EXTERNAL_CLOCK): cv.Schema( | ||||||
|                 cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, |                 { | ||||||
|                 cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( |                     cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number, | ||||||
|                     cv.frequency, cv.Range(min=8e6, max=20e6) |                     cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All( | ||||||
|                 ), |                         cv.frequency, cv.Range(min=8e6, max=20e6) | ||||||
|             } |                     ), | ||||||
|         ), |                 } | ||||||
|         cv.Required(CONF_I2C_PINS): cv.Schema( |             ), | ||||||
|             { |             cv.Optional(CONF_I2C_PINS): cv.Schema( | ||||||
|                 cv.Required(CONF_SDA): pins.internal_gpio_output_pin_number, |                 { | ||||||
|                 cv.Required(CONF_SCL): pins.internal_gpio_output_pin_number, |                     cv.Required(CONF_SDA): pins.internal_gpio_output_pin_number, | ||||||
|             } |                     cv.Required(CONF_SCL): pins.internal_gpio_output_pin_number, | ||||||
|         ), |                 } | ||||||
|         cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, |             ), | ||||||
|         cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, |             cv.Optional(CONF_I2C_ID): cv.Any( | ||||||
|         # image |                 cv.use_id(i2c.InternalI2CBus), | ||||||
|         cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum( |                 msg="I2C bus must be an internal ESP32 I2C bus", | ||||||
|             FRAME_SIZES, upper=True |             ), | ||||||
|         ), |             cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, | ||||||
|         cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=6, max=63), |             cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, | ||||||
|         cv.Optional(CONF_CONTRAST, default=0): camera_range_param, |             # image | ||||||
|         cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param, |             cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum( | ||||||
|         cv.Optional(CONF_SATURATION, default=0): camera_range_param, |                 FRAME_SIZES, upper=True | ||||||
|         cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, |             ), | ||||||
|         cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, |             cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=6, max=63), | ||||||
|         cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum( |             cv.Optional(CONF_CONTRAST, default=0): camera_range_param, | ||||||
|             ENUM_SPECIAL_EFFECT, upper=True |             cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param, | ||||||
|         ), |             cv.Optional(CONF_SATURATION, default=0): camera_range_param, | ||||||
|         # exposure |             cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, | ||||||
|         cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum( |             cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, | ||||||
|             ENUM_GAIN_CONTROL_MODE, upper=True |             cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum( | ||||||
|         ), |                 ENUM_SPECIAL_EFFECT, upper=True | ||||||
|         cv.Optional(CONF_AEC2, default=False): cv.boolean, |             ), | ||||||
|         cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, |             # exposure | ||||||
|         cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), |             cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum( | ||||||
|         # gains |                 ENUM_GAIN_CONTROL_MODE, upper=True | ||||||
|         cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum( |             ), | ||||||
|             ENUM_GAIN_CONTROL_MODE, upper=True |             cv.Optional(CONF_AEC2, default=False): cv.boolean, | ||||||
|         ), |             cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, | ||||||
|         cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30), |             cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), | ||||||
|         cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum( |             # gains | ||||||
|             ENUM_GAIN_CEILING, upper=True |             cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum( | ||||||
|         ), |                 ENUM_GAIN_CONTROL_MODE, upper=True | ||||||
|         # white balance |             ), | ||||||
|         cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum(ENUM_WB_MODE, upper=True), |             cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30), | ||||||
|         # test pattern |             cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum( | ||||||
|         cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, |                 ENUM_GAIN_CEILING, upper=True | ||||||
|         # framerates |             ), | ||||||
|         cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( |             # white balance | ||||||
|             cv.framerate, cv.Range(min=0, min_included=False, max=60) |             cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum( | ||||||
|         ), |                 ENUM_WB_MODE, upper=True | ||||||
|         cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( |             ), | ||||||
|             cv.framerate, cv.Range(min=0, max=1) |             # test pattern | ||||||
|         ), |             cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, | ||||||
|         cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), |             # framerates | ||||||
|         cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( |             cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( | ||||||
|             { |                 cv.framerate, cv.Range(min=0, min_included=False, max=60) | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( |             ), | ||||||
|                     ESP32CameraStreamStartTrigger |             cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( | ||||||
|                 ), |                 cv.framerate, cv.Range(min=0, max=1) | ||||||
|             } |             ), | ||||||
|         ), |             cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2), | ||||||
|         cv.Optional(CONF_ON_STREAM_STOP): automation.validate_automation( |             cv.Optional(CONF_ON_STREAM_START): automation.validate_automation( | ||||||
|             { |                 { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|                     ESP32CameraStreamStopTrigger |                         ESP32CameraStreamStartTrigger | ||||||
|                 ), |                     ), | ||||||
|             } |                 } | ||||||
|         ), |             ), | ||||||
|         cv.Optional(CONF_ON_IMAGE): automation.validate_automation( |             cv.Optional(CONF_ON_STREAM_STOP): automation.validate_automation( | ||||||
|             { |                 { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESP32CameraImageTrigger), |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|             } |                         ESP32CameraStreamStopTrigger | ||||||
|         ), |                     ), | ||||||
|     } |                 } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |             ), | ||||||
|  |             cv.Optional(CONF_ON_IMAGE): automation.validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                         ESP32CameraImageTrigger | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     cv.has_exactly_one_key(CONF_I2C_PINS, CONF_I2C_ID), | ||||||
|  | ) | ||||||
|  |  | ||||||
| SETTERS = { | SETTERS = { | ||||||
|     # pin assignment |     # pin assignment | ||||||
| @@ -280,8 +293,12 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     extclk = config[CONF_EXTERNAL_CLOCK] |     extclk = config[CONF_EXTERNAL_CLOCK] | ||||||
|     cg.add(var.set_external_clock(extclk[CONF_PIN], extclk[CONF_FREQUENCY])) |     cg.add(var.set_external_clock(extclk[CONF_PIN], extclk[CONF_FREQUENCY])) | ||||||
|     i2c_pins = config[CONF_I2C_PINS] |     if i2c_id := config.get(CONF_I2C_ID): | ||||||
|     cg.add(var.set_i2c_pins(i2c_pins[CONF_SDA], i2c_pins[CONF_SCL])) |         i2c_hub = await cg.get_variable(i2c_id) | ||||||
|  |         cg.add(var.set_i2c_id(i2c_hub)) | ||||||
|  |     else: | ||||||
|  |         i2c_pins = config[CONF_I2C_PINS] | ||||||
|  |         cg.add(var.set_i2c_pins(i2c_pins[CONF_SDA], i2c_pins[CONF_SCL])) | ||||||
|     cg.add(var.set_max_update_interval(1000 / config[CONF_MAX_FRAMERATE])) |     cg.add(var.set_max_update_interval(1000 / config[CONF_MAX_FRAMERATE])) | ||||||
|     if config[CONF_IDLE_FRAMERATE] == 0: |     if config[CONF_IDLE_FRAMERATE] == 0: | ||||||
|         cg.add(var.set_idle_update_interval(0)) |         cg.add(var.set_idle_update_interval(0)) | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include "esp32_camera.h" | #include "esp32_camera.h" | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/core/hal.h" |  | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #include <freertos/task.h> | #include <freertos/task.h> | ||||||
|  |  | ||||||
| @@ -16,6 +16,12 @@ static const char *const TAG = "esp32_camera"; | |||||||
| void ESP32Camera::setup() { | void ESP32Camera::setup() { | ||||||
|   global_esp32_camera = this; |   global_esp32_camera = this; | ||||||
|  |  | ||||||
|  | #ifdef USE_I2C | ||||||
|  |   if (this->i2c_bus_ != nullptr) { | ||||||
|  |     this->config_.sccb_i2c_port = this->i2c_bus_->get_port(); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   /* initialize time to now */ |   /* initialize time to now */ | ||||||
|   this->last_update_ = millis(); |   this->last_update_ = millis(); | ||||||
|  |  | ||||||
| @@ -57,7 +63,7 @@ void ESP32Camera::dump_config() { | |||||||
|                 "  External Clock: Pin:%d Frequency:%u\n" |                 "  External Clock: Pin:%d Frequency:%u\n" | ||||||
|                 "  I2C Pins: SDA:%d SCL:%d\n" |                 "  I2C Pins: SDA:%d SCL:%d\n" | ||||||
|                 "  Reset Pin: %d", |                 "  Reset Pin: %d", | ||||||
|                 this->name_.c_str(), YESNO(this->internal_), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3, |                 this->name_.c_str(), YESNO(this->is_internal()), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3, | ||||||
|                 conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7, conf.pin_vsync, conf.pin_href, conf.pin_pclk, |                 conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7, conf.pin_vsync, conf.pin_href, conf.pin_pclk, | ||||||
|                 conf.pin_xclk, conf.xclk_freq_hz, conf.pin_sccb_sda, conf.pin_sccb_scl, conf.pin_reset); |                 conf.pin_xclk, conf.xclk_freq_hz, conf.pin_sccb_sda, conf.pin_sccb_scl, conf.pin_reset); | ||||||
|   switch (this->config_.frame_size) { |   switch (this->config_.frame_size) { | ||||||
| @@ -246,6 +252,13 @@ void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) { | |||||||
|   this->config_.pin_sccb_sda = sda; |   this->config_.pin_sccb_sda = sda; | ||||||
|   this->config_.pin_sccb_scl = scl; |   this->config_.pin_sccb_scl = scl; | ||||||
| } | } | ||||||
|  | #ifdef USE_I2C | ||||||
|  | void ESP32Camera::set_i2c_id(i2c::InternalI2CBus *i2c_bus) { | ||||||
|  |   this->i2c_bus_ = i2c_bus; | ||||||
|  |   this->config_.pin_sccb_sda = -1; | ||||||
|  |   this->config_.pin_sccb_scl = -1; | ||||||
|  | } | ||||||
|  | #endif  // USE_I2C | ||||||
| void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } | void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } | ||||||
| void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } | void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,13 +2,17 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <esp_camera.h> | ||||||
|  | #include <freertos/FreeRTOS.h> | ||||||
|  | #include <freertos/queue.h> | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/entity_base.h" | #include "esphome/core/entity_base.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include <esp_camera.h> |  | ||||||
| #include <freertos/FreeRTOS.h> | #ifdef USE_I2C | ||||||
| #include <freertos/queue.h> | #include "esphome/components/i2c/i2c_bus.h" | ||||||
|  | #endif  // USE_I2C | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_camera { | namespace esp32_camera { | ||||||
| @@ -118,6 +122,9 @@ class ESP32Camera : public EntityBase, public Component { | |||||||
|   void set_pixel_clock_pin(uint8_t pin); |   void set_pixel_clock_pin(uint8_t pin); | ||||||
|   void set_external_clock(uint8_t pin, uint32_t frequency); |   void set_external_clock(uint8_t pin, uint32_t frequency); | ||||||
|   void set_i2c_pins(uint8_t sda, uint8_t scl); |   void set_i2c_pins(uint8_t sda, uint8_t scl); | ||||||
|  | #ifdef USE_I2C | ||||||
|  |   void set_i2c_id(i2c::InternalI2CBus *i2c_bus); | ||||||
|  | #endif  // USE_I2C | ||||||
|   void set_reset_pin(uint8_t pin); |   void set_reset_pin(uint8_t pin); | ||||||
|   void set_power_down_pin(uint8_t pin); |   void set_power_down_pin(uint8_t pin); | ||||||
|   /* -- image */ |   /* -- image */ | ||||||
| @@ -210,6 +217,9 @@ class ESP32Camera : public EntityBase, public Component { | |||||||
|  |  | ||||||
|   uint32_t last_idle_request_{0}; |   uint32_t last_idle_request_{0}; | ||||||
|   uint32_t last_update_{0}; |   uint32_t last_update_{0}; | ||||||
|  | #ifdef USE_I2C | ||||||
|  |   i2c::InternalI2CBus *i2c_bus_{nullptr}; | ||||||
|  | #endif  // USE_I2C | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import esphome.config_validation as cv | |||||||
| from esphome.const import CONF_ID, CONF_MODE, CONF_PORT | from esphome.const import CONF_ID, CONF_MODE, CONF_PORT | ||||||
|  |  | ||||||
| CODEOWNERS = ["@ayufan"] | CODEOWNERS = ["@ayufan"] | ||||||
| DEPENDENCIES = ["esp32_camera"] | DEPENDENCIES = ["esp32_camera", "network"] | ||||||
| MULTI_CONF = True | MULTI_CONF = True | ||||||
|  |  | ||||||
| esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") | esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") | ||||||
|   | |||||||
| @@ -1,25 +0,0 @@ | |||||||
| #ifdef USE_ESP32 |  | ||||||
| #include "esp32_hall.h" |  | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/core/hal.h" |  | ||||||
| #include <driver/adc.h> |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace esp32_hall { |  | ||||||
|  |  | ||||||
| static const char *const TAG = "esp32_hall"; |  | ||||||
|  |  | ||||||
| void ESP32HallSensor::update() { |  | ||||||
|   adc1_config_width(ADC_WIDTH_BIT_12); |  | ||||||
|   int value_int = hall_sensor_read(); |  | ||||||
|   float value = (value_int / 4095.0f) * 10000.0f; |  | ||||||
|   ESP_LOGD(TAG, "'%s': Got reading %.0f µT", this->name_.c_str(), value); |  | ||||||
|   this->publish_state(value); |  | ||||||
| } |  | ||||||
| std::string ESP32HallSensor::unique_id() { return get_mac_address() + "-hall"; } |  | ||||||
| void ESP32HallSensor::dump_config() { LOG_SENSOR("", "ESP32 Hall Sensor", this); } |  | ||||||
|  |  | ||||||
| }  // namespace esp32_hall |  | ||||||
| }  // namespace esphome |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/components/sensor/sensor.h" |  | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace esp32_hall { |  | ||||||
|  |  | ||||||
| class ESP32HallSensor : public sensor::Sensor, public PollingComponent { |  | ||||||
|  public: |  | ||||||
|   void dump_config() override; |  | ||||||
|  |  | ||||||
|   void update() override; |  | ||||||
|  |  | ||||||
|   std::string unique_id() override; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| }  // namespace esp32_hall |  | ||||||
| }  // namespace esphome |  | ||||||
|  |  | ||||||
| #endif |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| from esphome.components import sensor |  | ||||||
| import esphome.config_validation as cv |  | ||||||
| from esphome.const import ICON_MAGNET, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA |  | ||||||
|  |  | ||||||
| DEPENDENCIES = ["esp32"] |  | ||||||
|  |  | ||||||
| esp32_hall_ns = cg.esphome_ns.namespace("esp32_hall") |  | ||||||
| ESP32HallSensor = esp32_hall_ns.class_( |  | ||||||
|     "ESP32HallSensor", sensor.Sensor, cg.PollingComponent |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.sensor_schema( |  | ||||||
|     ESP32HallSensor, |  | ||||||
|     unit_of_measurement=UNIT_MICROTESLA, |  | ||||||
|     icon=ICON_MAGNET, |  | ||||||
|     accuracy_decimals=1, |  | ||||||
|     state_class=STATE_CLASS_MEASUREMENT, |  | ||||||
| ).extend(cv.polling_component_schema("60s")) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): |  | ||||||
|     var = await sensor.new_sensor(config) |  | ||||||
|     await cg.register_component(var, config) |  | ||||||
| @@ -168,6 +168,8 @@ void ESP32ImprovComponent::loop() { | |||||||
|     case improv::STATE_PROVISIONED: { |     case improv::STATE_PROVISIONED: { | ||||||
|       this->incoming_data_.clear(); |       this->incoming_data_.clear(); | ||||||
|       this->set_status_indicator_state_(false); |       this->set_status_indicator_state_(false); | ||||||
|  |       // Provisioning complete, no further loop execution needed | ||||||
|  |       this->disable_loop(); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -254,6 +256,7 @@ void ESP32ImprovComponent::start() { | |||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Setting Improv to start"); |   ESP_LOGD(TAG, "Setting Improv to start"); | ||||||
|   this->should_start_ = true; |   this->should_start_ = true; | ||||||
|  |   this->enable_loop(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32ImprovComponent::stop() { | void ESP32ImprovComponent::stop() { | ||||||
|   | |||||||
| @@ -1,48 +1,8 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| from esphome.components import esp32 | from esphome.components import esp32 | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION |  | ||||||
| from esphome.core import CORE |  | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
|  |  | ||||||
| RMT_TX_CHANNELS = { |  | ||||||
|     esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], |  | ||||||
|     esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], |  | ||||||
|     esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3], |  | ||||||
|     esp32.const.VARIANT_ESP32C3: [0, 1], |  | ||||||
|     esp32.const.VARIANT_ESP32C6: [0, 1], |  | ||||||
|     esp32.const.VARIANT_ESP32H2: [0, 1], |  | ||||||
| } |  | ||||||
|  |  | ||||||
| RMT_RX_CHANNELS = { |  | ||||||
|     esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7], |  | ||||||
|     esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3], |  | ||||||
|     esp32.const.VARIANT_ESP32S3: [4, 5, 6, 7], |  | ||||||
|     esp32.const.VARIANT_ESP32C3: [2, 3], |  | ||||||
|     esp32.const.VARIANT_ESP32C6: [2, 3], |  | ||||||
|     esp32.const.VARIANT_ESP32H2: [2, 3], |  | ||||||
| } |  | ||||||
|  |  | ||||||
| rmt_channel_t = cg.global_ns.enum("rmt_channel_t") |  | ||||||
| RMT_CHANNEL_ENUMS = { |  | ||||||
|     0: rmt_channel_t.RMT_CHANNEL_0, |  | ||||||
|     1: rmt_channel_t.RMT_CHANNEL_1, |  | ||||||
|     2: rmt_channel_t.RMT_CHANNEL_2, |  | ||||||
|     3: rmt_channel_t.RMT_CHANNEL_3, |  | ||||||
|     4: rmt_channel_t.RMT_CHANNEL_4, |  | ||||||
|     5: rmt_channel_t.RMT_CHANNEL_5, |  | ||||||
|     6: rmt_channel_t.RMT_CHANNEL_6, |  | ||||||
|     7: rmt_channel_t.RMT_CHANNEL_7, |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def use_new_rmt_driver(): |  | ||||||
|     framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] |  | ||||||
|     if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0): |  | ||||||
|         return True |  | ||||||
|     return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_clock_resolution(): | def validate_clock_resolution(): | ||||||
|     def _validator(value): |     def _validator(value): | ||||||
| @@ -60,21 +20,3 @@ def validate_clock_resolution(): | |||||||
|         return value |         return value | ||||||
|  |  | ||||||
|     return _validator |     return _validator | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_rmt_channel(*, tx: bool): |  | ||||||
|     rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS |  | ||||||
|  |  | ||||||
|     def _validator(value): |  | ||||||
|         cv.only_on_esp32(value) |  | ||||||
|         value = cv.int_(value) |  | ||||||
|         variant = esp32.get_esp32_variant() |  | ||||||
|         if variant not in rmt_channels: |  | ||||||
|             raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.") |  | ||||||
|         if value not in rmt_channels[variant]: |  | ||||||
|             raise cv.Invalid( |  | ||||||
|                 f"RMT channel {value} does not support {'transmitting' if tx else 'receiving'} for ESP32 variant {variant}." |  | ||||||
|             ) |  | ||||||
|         return cv.enum(RMT_CHANNEL_ENUMS)(value) |  | ||||||
|  |  | ||||||
|     return _validator |  | ||||||
|   | |||||||
| @@ -42,7 +42,6 @@ void ESP32RMTLEDStripLightOutput::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
|   RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL); |   RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL); | ||||||
|  |  | ||||||
|   // 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset |   // 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset | ||||||
| @@ -79,36 +78,6 @@ void ESP32RMTLEDStripLightOutput::setup() { | |||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| #else |  | ||||||
|   RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL); |  | ||||||
|  |  | ||||||
|   // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset |  | ||||||
|   this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1); |  | ||||||
|  |  | ||||||
|   rmt_config_t config; |  | ||||||
|   memset(&config, 0, sizeof(config)); |  | ||||||
|   config.channel = this->channel_; |  | ||||||
|   config.rmt_mode = RMT_MODE_TX; |  | ||||||
|   config.gpio_num = gpio_num_t(this->pin_); |  | ||||||
|   config.mem_block_num = 1; |  | ||||||
|   config.clk_div = RMT_CLK_DIV; |  | ||||||
|   config.tx_config.loop_en = false; |  | ||||||
|   config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; |  | ||||||
|   config.tx_config.carrier_en = false; |  | ||||||
|   config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; |  | ||||||
|   config.tx_config.idle_output_en = true; |  | ||||||
|  |  | ||||||
|   if (rmt_config(&config) != ESP_OK) { |  | ||||||
|     ESP_LOGE(TAG, "Cannot initialize RMT!"); |  | ||||||
|     this->mark_failed(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) { |  | ||||||
|     ESP_LOGE(TAG, "Cannot install RMT driver!"); |  | ||||||
|     this->mark_failed(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, | ||||||
| @@ -145,11 +114,7 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | |||||||
|  |  | ||||||
|   ESP_LOGVV(TAG, "Writing RGB values to bus"); |   ESP_LOGVV(TAG, "Writing RGB values to bus"); | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
|   esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000); |   esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000); | ||||||
| #else |  | ||||||
|   esp_err_t error = rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)); |  | ||||||
| #endif |  | ||||||
|   if (error != ESP_OK) { |   if (error != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "RMT TX timeout"); |     ESP_LOGE(TAG, "RMT TX timeout"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
| @@ -162,11 +127,7 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | |||||||
|   size_t size = 0; |   size_t size = 0; | ||||||
|   size_t len = 0; |   size_t len = 0; | ||||||
|   uint8_t *psrc = this->buf_; |   uint8_t *psrc = this->buf_; | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
|   rmt_symbol_word_t *pdest = this->rmt_buf_; |   rmt_symbol_word_t *pdest = this->rmt_buf_; | ||||||
| #else |  | ||||||
|   rmt_item32_t *pdest = this->rmt_buf_; |  | ||||||
| #endif |  | ||||||
|   while (size < buffer_size) { |   while (size < buffer_size) { | ||||||
|     uint8_t b = *psrc; |     uint8_t b = *psrc; | ||||||
|     for (int i = 0; i < 8; i++) { |     for (int i = 0; i < 8; i++) { | ||||||
| @@ -184,15 +145,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) { | |||||||
|     len++; |     len++; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
|   rmt_transmit_config_t config; |   rmt_transmit_config_t config; | ||||||
|   memset(&config, 0, sizeof(config)); |   memset(&config, 0, sizeof(config)); | ||||||
|   config.loop_count = 0; |   config.loop_count = 0; | ||||||
|   config.flags.eot_level = 0; |   config.flags.eot_level = 0; | ||||||
|   error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config); |   error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config); | ||||||
| #else |  | ||||||
|   error = rmt_write_items(this->channel_, this->rmt_buf_, len, false); |  | ||||||
| #endif |  | ||||||
|   if (error != ESP_OK) { |   if (error != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "RMT TX error"); |     ESP_LOGE(TAG, "RMT TX error"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
| @@ -251,11 +208,7 @@ void ESP32RMTLEDStripLightOutput::dump_config() { | |||||||
|                 "ESP32 RMT LED Strip:\n" |                 "ESP32 RMT LED Strip:\n" | ||||||
|                 "  Pin: %u", |                 "  Pin: %u", | ||||||
|                 this->pin_); |                 this->pin_); | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
|   ESP_LOGCONFIG(TAG, "  RMT Symbols: %" PRIu32, this->rmt_symbols_); |   ESP_LOGCONFIG(TAG, "  RMT Symbols: %" PRIu32, this->rmt_symbols_); | ||||||
| #else |  | ||||||
|   ESP_LOGCONFIG(TAG, "  Channel: %u", this->channel_); |  | ||||||
| #endif |  | ||||||
|   const char *rgb_order; |   const char *rgb_order; | ||||||
|   switch (this->rgb_order_) { |   switch (this->rgb_order_) { | ||||||
|     case ORDER_RGB: |     case ORDER_RGB: | ||||||
|   | |||||||
| @@ -11,12 +11,7 @@ | |||||||
| #include <driver/gpio.h> | #include <driver/gpio.h> | ||||||
| #include <esp_err.h> | #include <esp_err.h> | ||||||
| #include <esp_idf_version.h> | #include <esp_idf_version.h> | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
| #include <driver/rmt_tx.h> | #include <driver/rmt_tx.h> | ||||||
| #else |  | ||||||
| #include <driver/rmt.h> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_rmt_led_strip { | namespace esp32_rmt_led_strip { | ||||||
| @@ -61,11 +56,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|                       uint32_t reset_time_high, uint32_t reset_time_low); |                       uint32_t reset_time_high, uint32_t reset_time_low); | ||||||
|  |  | ||||||
|   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } |   void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; } | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
|   void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; } |   void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; } | ||||||
| #else |  | ||||||
|   void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   void clear_effect_data() override { |   void clear_effect_data() override { | ||||||
|     for (int i = 0; i < this->size(); i++) |     for (int i = 0; i < this->size(); i++) | ||||||
| @@ -81,17 +72,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|  |  | ||||||
|   uint8_t *buf_{nullptr}; |   uint8_t *buf_{nullptr}; | ||||||
|   uint8_t *effect_data_{nullptr}; |   uint8_t *effect_data_{nullptr}; | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 |  | ||||||
|   rmt_channel_handle_t channel_{nullptr}; |   rmt_channel_handle_t channel_{nullptr}; | ||||||
|   rmt_encoder_handle_t encoder_{nullptr}; |   rmt_encoder_handle_t encoder_{nullptr}; | ||||||
|   rmt_symbol_word_t *rmt_buf_{nullptr}; |   rmt_symbol_word_t *rmt_buf_{nullptr}; | ||||||
|   rmt_symbol_word_t bit0_, bit1_, reset_; |   rmt_symbol_word_t bit0_, bit1_, reset_; | ||||||
|   uint32_t rmt_symbols_{48}; |   uint32_t rmt_symbols_{48}; | ||||||
| #else |  | ||||||
|   rmt_item32_t *rmt_buf_{nullptr}; |  | ||||||
|   rmt_item32_t bit0_, bit1_, reset_; |  | ||||||
|   rmt_channel_t channel_{RMT_CHANNEL_0}; |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   uint8_t pin_; |   uint8_t pin_; | ||||||
|   uint16_t num_leds_; |   uint16_t num_leds_; | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import logging | |||||||
|  |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components import esp32, esp32_rmt, light | from esphome.components import esp32, light | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_CHIPSET, |     CONF_CHIPSET, | ||||||
| @@ -13,11 +13,9 @@ from esphome.const import ( | |||||||
|     CONF_OUTPUT_ID, |     CONF_OUTPUT_ID, | ||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
|     CONF_RGB_ORDER, |     CONF_RGB_ORDER, | ||||||
|     CONF_RMT_CHANNEL, |  | ||||||
|     CONF_RMT_SYMBOLS, |     CONF_RMT_SYMBOLS, | ||||||
|     CONF_USE_DMA, |     CONF_USE_DMA, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE |  | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @@ -69,53 +67,6 @@ CONF_RESET_HIGH = "reset_high" | |||||||
| CONF_RESET_LOW = "reset_low" | CONF_RESET_LOW = "reset_low" | ||||||
|  |  | ||||||
|  |  | ||||||
| class OptionalForIDF5(cv.SplitDefault): |  | ||||||
|     @property |  | ||||||
|     def default(self): |  | ||||||
|         if not esp32_rmt.use_new_rmt_driver(): |  | ||||||
|             return cv.UNDEFINED |  | ||||||
|         return super().default |  | ||||||
|  |  | ||||||
|     @default.setter |  | ||||||
|     def default(self, value): |  | ||||||
|         # Ignore default set from vol.Optional |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def only_with_new_rmt_driver(obj): |  | ||||||
|     if not esp32_rmt.use_new_rmt_driver(): |  | ||||||
|         raise cv.Invalid( |  | ||||||
|             "This feature is only available for the IDF framework version 5." |  | ||||||
|         ) |  | ||||||
|     return obj |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def not_with_new_rmt_driver(obj): |  | ||||||
|     if esp32_rmt.use_new_rmt_driver(): |  | ||||||
|         raise cv.Invalid( |  | ||||||
|             "This feature is not available for the IDF framework version 5." |  | ||||||
|         ) |  | ||||||
|     return obj |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def final_validation(config): |  | ||||||
|     if not esp32_rmt.use_new_rmt_driver(): |  | ||||||
|         if CONF_RMT_CHANNEL not in config: |  | ||||||
|             if CORE.using_esp_idf: |  | ||||||
|                 raise cv.Invalid( |  | ||||||
|                     "rmt_channel is a required option for IDF version < 5." |  | ||||||
|                 ) |  | ||||||
|             raise cv.Invalid( |  | ||||||
|                 "rmt_channel is a required option for the Arduino framework." |  | ||||||
|             ) |  | ||||||
|         _LOGGER.warning( |  | ||||||
|             "RMT_LED_STRIP support for IDF version < 5 is deprecated and will be removed soon." |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FINAL_VALIDATE_SCHEMA = final_validation |  | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     light.ADDRESSABLE_LIGHT_SCHEMA.extend( |     light.ADDRESSABLE_LIGHT_SCHEMA.extend( | ||||||
|         { |         { | ||||||
| @@ -123,20 +74,17 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, |             cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, | ||||||
|             cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, |             cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, | ||||||
|             cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), |             cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), | ||||||
|             cv.Optional(CONF_RMT_CHANNEL): cv.All( |             cv.SplitDefault( | ||||||
|                 not_with_new_rmt_driver, esp32_rmt.validate_rmt_channel(tx=True) |  | ||||||
|             ), |  | ||||||
|             OptionalForIDF5( |  | ||||||
|                 CONF_RMT_SYMBOLS, |                 CONF_RMT_SYMBOLS, | ||||||
|                 esp32_idf=192, |                 esp32=192, | ||||||
|                 esp32_s2_idf=192, |                 esp32_s2=192, | ||||||
|                 esp32_s3_idf=192, |                 esp32_s3=192, | ||||||
|                 esp32_p4_idf=192, |                 esp32_p4=192, | ||||||
|                 esp32_c3_idf=96, |                 esp32_c3=96, | ||||||
|                 esp32_c5_idf=96, |                 esp32_c5=96, | ||||||
|                 esp32_c6_idf=96, |                 esp32_c6=96, | ||||||
|                 esp32_h2_idf=96, |                 esp32_h2=96, | ||||||
|             ): cv.All(only_with_new_rmt_driver, cv.int_range(min=2)), |             ): cv.int_range(min=2), | ||||||
|             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, |             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, | ||||||
|             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), |             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), | ||||||
|             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, |             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, | ||||||
| @@ -145,7 +93,6 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 esp32.only_on_variant( |                 esp32.only_on_variant( | ||||||
|                     supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] |                     supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] | ||||||
|                 ), |                 ), | ||||||
|                 cv.only_with_esp_idf, |  | ||||||
|                 cv.boolean, |                 cv.boolean, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_USE_PSRAM, default=True): cv.boolean, |             cv.Optional(CONF_USE_PSRAM, default=True): cv.boolean, | ||||||
| @@ -218,15 +165,6 @@ async def to_code(config): | |||||||
|     cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) |     cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) | ||||||
|     cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) |     cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) | ||||||
|     cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) |     cg.add(var.set_use_psram(config[CONF_USE_PSRAM])) | ||||||
|  |     cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) | ||||||
|     if esp32_rmt.use_new_rmt_driver(): |     if CONF_USE_DMA in config: | ||||||
|         cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS])) |         cg.add(var.set_use_dma(config[CONF_USE_DMA])) | ||||||
|         if CONF_USE_DMA in config: |  | ||||||
|             cg.add(var.set_use_dma(config[CONF_USE_DMA])) |  | ||||||
|     else: |  | ||||||
|         rmt_channel_t = cg.global_ns.enum("rmt_channel_t") |  | ||||||
|         cg.add( |  | ||||||
|             var.set_rmt_channel( |  | ||||||
|                 getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}") |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|   | |||||||
| @@ -183,6 +183,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add_platformio_option("board", config[CONF_BOARD]) |     cg.add_platformio_option("board", config[CONF_BOARD]) | ||||||
|     cg.add_build_flag("-DUSE_ESP8266") |     cg.add_build_flag("-DUSE_ESP8266") | ||||||
|  |     cg.set_cpp_standard("gnu++17") | ||||||
|     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) |     cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) | ||||||
|     cg.add_define("ESPHOME_VARIANT", "ESP8266") |     cg.add_define("ESPHOME_VARIANT", "ESP8266") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,19 +26,19 @@ void ESPHomeOTAComponent::setup() { | |||||||
|   ota::register_ota_platform(this); |   ota::register_ota_platform(this); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0);  // monitored for incoming connections |   this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0);  // monitored for incoming connections | ||||||
|   if (server_ == nullptr) { |   if (this->server_ == nullptr) { | ||||||
|     ESP_LOGW(TAG, "Could not create socket"); |     ESP_LOGW(TAG, "Could not create socket"); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   int enable = 1; |   int enable = 1; | ||||||
|   int err = server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); |   int err = this->server_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); | ||||||
|   if (err != 0) { |   if (err != 0) { | ||||||
|     ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); |     ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); | ||||||
|     // we can still continue |     // we can still continue | ||||||
|   } |   } | ||||||
|   err = server_->setblocking(false); |   err = this->server_->setblocking(false); | ||||||
|   if (err != 0) { |   if (err != 0) { | ||||||
|     ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); |     ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
| @@ -54,14 +54,14 @@ void ESPHomeOTAComponent::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   err = server_->bind((struct sockaddr *) &server, sizeof(server)); |   err = this->server_->bind((struct sockaddr *) &server, sizeof(server)); | ||||||
|   if (err != 0) { |   if (err != 0) { | ||||||
|     ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); |     ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   err = server_->listen(4); |   err = this->server_->listen(4); | ||||||
|   if (err != 0) { |   if (err != 0) { | ||||||
|     ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); |     ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
| @@ -82,7 +82,14 @@ void ESPHomeOTAComponent::dump_config() { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESPHomeOTAComponent::loop() { this->handle_(); } | void ESPHomeOTAComponent::loop() { | ||||||
|  |   // Skip handle_() call if no client connected and no incoming connections | ||||||
|  |   // This optimization reduces idle loop overhead when OTA is not active | ||||||
|  |   // Note: No need to check server_ for null as the component is marked failed in setup() if server_ creation fails | ||||||
|  |   if (this->client_ != nullptr || this->server_->ready()) { | ||||||
|  |     this->handle_(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; | static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; | ||||||
|  |  | ||||||
| @@ -101,23 +108,21 @@ void ESPHomeOTAComponent::handle_() { | |||||||
|   size_t size_acknowledged = 0; |   size_t size_acknowledged = 0; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   if (client_ == nullptr) { |   if (this->client_ == nullptr) { | ||||||
|     // Check if the server socket is ready before accepting |     // We already checked server_->ready() in loop(), so we can accept directly | ||||||
|     if (this->server_->ready()) { |     struct sockaddr_storage source_addr; | ||||||
|       struct sockaddr_storage source_addr; |     socklen_t addr_len = sizeof(source_addr); | ||||||
|       socklen_t addr_len = sizeof(source_addr); |     this->client_ = this->server_->accept((struct sockaddr *) &source_addr, &addr_len); | ||||||
|       client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len); |     if (this->client_ == nullptr) | ||||||
|     } |       return; | ||||||
|   } |   } | ||||||
|   if (client_ == nullptr) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   int enable = 1; |   int enable = 1; | ||||||
|   int err = client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); |   int err = this->client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int)); | ||||||
|   if (err != 0) { |   if (err != 0) { | ||||||
|     ESP_LOGW(TAG, "Socket could not enable TCP nodelay, errno %d", errno); |     ESP_LOGW(TAG, "Socket could not enable TCP nodelay, errno %d", errno); | ||||||
|     client_->close(); |     this->client_->close(); | ||||||
|     client_ = nullptr; |     this->client_ = nullptr; | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ void EthernetComponent::setup() { | |||||||
|       .post_cb = nullptr, |       .post_cb = nullptr, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| #if USE_ESP_IDF && (ESP_IDF_VERSION_MAJOR >= 5) | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|   eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); |   eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); | ||||||
| #else | #else | ||||||
|   spi_device_handle_t spi_handle = nullptr; |   spi_device_handle_t spi_handle = nullptr; | ||||||
| @@ -274,6 +274,9 @@ void EthernetComponent::loop() { | |||||||
|         ESP_LOGW(TAG, "Connection lost; reconnecting"); |         ESP_LOGW(TAG, "Connection lost; reconnecting"); | ||||||
|         this->state_ = EthernetComponentState::CONNECTING; |         this->state_ = EthernetComponentState::CONNECTING; | ||||||
|         this->start_connect_(); |         this->start_connect_(); | ||||||
|  |       } else { | ||||||
|  |         // When connected and stable, disable the loop to save CPU cycles | ||||||
|  |         this->disable_loop(); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
| @@ -397,11 +400,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base | |||||||
|     case ETHERNET_EVENT_START: |     case ETHERNET_EVENT_START: | ||||||
|       event_name = "ETH started"; |       event_name = "ETH started"; | ||||||
|       global_eth_component->started_ = true; |       global_eth_component->started_ = true; | ||||||
|  |       global_eth_component->enable_loop_soon_any_context(); | ||||||
|       break; |       break; | ||||||
|     case ETHERNET_EVENT_STOP: |     case ETHERNET_EVENT_STOP: | ||||||
|       event_name = "ETH stopped"; |       event_name = "ETH stopped"; | ||||||
|       global_eth_component->started_ = false; |       global_eth_component->started_ = false; | ||||||
|       global_eth_component->connected_ = false; |       global_eth_component->connected_ = false; | ||||||
|  |       global_eth_component->enable_loop_soon_any_context();  // Enable loop when connection state changes | ||||||
|       break; |       break; | ||||||
|     case ETHERNET_EVENT_CONNECTED: |     case ETHERNET_EVENT_CONNECTED: | ||||||
|       event_name = "ETH connected"; |       event_name = "ETH connected"; | ||||||
| @@ -409,6 +414,7 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base | |||||||
|     case ETHERNET_EVENT_DISCONNECTED: |     case ETHERNET_EVENT_DISCONNECTED: | ||||||
|       event_name = "ETH disconnected"; |       event_name = "ETH disconnected"; | ||||||
|       global_eth_component->connected_ = false; |       global_eth_component->connected_ = false; | ||||||
|  |       global_eth_component->enable_loop_soon_any_context();  // Enable loop when connection state changes | ||||||
|       break; |       break; | ||||||
|     default: |     default: | ||||||
|       return; |       return; | ||||||
| @@ -425,8 +431,10 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b | |||||||
|   global_eth_component->got_ipv4_address_ = true; |   global_eth_component->got_ipv4_address_ = true; | ||||||
| #if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) | #if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) | ||||||
|   global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; |   global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; | ||||||
|  |   global_eth_component->enable_loop_soon_any_context();  // Enable loop when connection state changes | ||||||
| #else | #else | ||||||
|   global_eth_component->connected_ = true; |   global_eth_component->connected_ = true; | ||||||
|  |   global_eth_component->enable_loop_soon_any_context();  // Enable loop when connection state changes | ||||||
| #endif /* USE_NETWORK_IPV6 */ | #endif /* USE_NETWORK_IPV6 */ | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -439,8 +447,10 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_ | |||||||
| #if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) | #if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0) | ||||||
|   global_eth_component->connected_ = |   global_eth_component->connected_ = | ||||||
|       global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); |       global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); | ||||||
|  |   global_eth_component->enable_loop_soon_any_context();  // Enable loop when connection state changes | ||||||
| #else | #else | ||||||
|   global_eth_component->connected_ = global_eth_component->got_ipv4_address_; |   global_eth_component->connected_ = global_eth_component->got_ipv4_address_; | ||||||
|  |   global_eth_component->enable_loop_soon_any_context();  // Enable loop when connection state changes | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| #endif /* USE_NETWORK_IPV6 */ | #endif /* USE_NETWORK_IPV6 */ | ||||||
| @@ -620,6 +630,7 @@ bool EthernetComponent::powerdown() { | |||||||
|   } |   } | ||||||
|   this->connected_ = false; |   this->connected_ = false; | ||||||
|   this->started_ = false; |   this->started_ = false; | ||||||
|  |   // No need to enable_loop() here as this is only called during shutdown/reboot | ||||||
|   if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { |   if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "Error powering down ethernet PHY"); |     ESP_LOGE(TAG, "Error powering down ethernet PHY"); | ||||||
|     return false; |     return false; | ||||||
|   | |||||||
| @@ -41,39 +41,48 @@ void FanCall::perform() { | |||||||
| void FanCall::validate_() { | void FanCall::validate_() { | ||||||
|   auto traits = this->parent_.get_traits(); |   auto traits = this->parent_.get_traits(); | ||||||
|  |  | ||||||
|   if (this->speed_.has_value()) |   if (this->speed_.has_value()) { | ||||||
|     this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count()); |     this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count()); | ||||||
|  |  | ||||||
|   if (this->binary_state_.has_value() && *this->binary_state_) { |     // https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes | ||||||
|     // when turning on, if neither current nor new speed available, set speed to 100% |     // "Manually setting a speed must disable any set preset mode" | ||||||
|     if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0 && !this->speed_.has_value()) { |     this->preset_mode_.clear(); | ||||||
|       this->speed_ = traits.supported_speed_count(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (this->oscillating_.has_value() && !traits.supports_oscillation()) { |  | ||||||
|     ESP_LOGW(TAG, "'%s' - This fan does not support oscillation!", this->parent_.get_name().c_str()); |  | ||||||
|     this->oscillating_.reset(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (this->speed_.has_value() && !traits.supports_speed()) { |  | ||||||
|     ESP_LOGW(TAG, "'%s' - This fan does not support speeds!", this->parent_.get_name().c_str()); |  | ||||||
|     this->speed_.reset(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (this->direction_.has_value() && !traits.supports_direction()) { |  | ||||||
|     ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str()); |  | ||||||
|     this->direction_.reset(); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!this->preset_mode_.empty()) { |   if (!this->preset_mode_.empty()) { | ||||||
|     const auto &preset_modes = traits.supported_preset_modes(); |     const auto &preset_modes = traits.supported_preset_modes(); | ||||||
|     if (preset_modes.find(this->preset_mode_) == preset_modes.end()) { |     if (preset_modes.find(this->preset_mode_) == preset_modes.end()) { | ||||||
|       ESP_LOGW(TAG, "'%s' - This fan does not support preset mode '%s'!", this->parent_.get_name().c_str(), |       ESP_LOGW(TAG, "%s: Preset mode '%s' not supported", this->parent_.get_name().c_str(), this->preset_mode_.c_str()); | ||||||
|                this->preset_mode_.c_str()); |  | ||||||
|       this->preset_mode_.clear(); |       this->preset_mode_.clear(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // when turning on... | ||||||
|  |   if (!this->parent_.state && this->binary_state_.has_value() && | ||||||
|  |       *this->binary_state_ | ||||||
|  |       // ..,and no preset mode will be active... | ||||||
|  |       && this->preset_mode_.empty() && | ||||||
|  |       this->parent_.preset_mode.empty() | ||||||
|  |       // ...and neither current nor new speed is available... | ||||||
|  |       && traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) { | ||||||
|  |     // ...set speed to 100% | ||||||
|  |     this->speed_ = traits.supported_speed_count(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->oscillating_.has_value() && !traits.supports_oscillation()) { | ||||||
|  |     ESP_LOGW(TAG, "%s: Oscillation not supported", this->parent_.get_name().c_str()); | ||||||
|  |     this->oscillating_.reset(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->speed_.has_value() && !traits.supports_speed()) { | ||||||
|  |     ESP_LOGW(TAG, "%s: Speed control not supported", this->parent_.get_name().c_str()); | ||||||
|  |     this->speed_.reset(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->direction_.has_value() && !traits.supports_direction()) { | ||||||
|  |     ESP_LOGW(TAG, "%s: Direction control not supported", this->parent_.get_name().c_str()); | ||||||
|  |     this->direction_.reset(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| FanCall FanRestoreState::to_call(Fan &fan) { | FanCall FanRestoreState::to_call(Fan &fan) { | ||||||
|   | |||||||
| @@ -67,10 +67,10 @@ class Font | |||||||
|   inline int get_height() { return this->height_; } |   inline int get_height() { return this->height_; } | ||||||
|   inline int get_bpp() { return this->bpp_; } |   inline int get_bpp() { return this->bpp_; } | ||||||
|  |  | ||||||
|   const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } |   const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_; |   std::vector<Glyph, RAMAllocator<Glyph>> glyphs_; | ||||||
|   int baseline_; |   int baseline_; | ||||||
|   int height_; |   int height_; | ||||||
|   uint8_t bpp_;  // bits per pixel |   uint8_t bpp_;  // bits per pixel | ||||||
|   | |||||||
| @@ -125,6 +125,6 @@ async def to_code(config): | |||||||
|     cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) |     cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) | ||||||
|     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) |     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) | ||||||
|  |  | ||||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.32") |     cg.add_library("tonia/HeatpumpIR", "1.0.35") | ||||||
|     if CORE.is_libretiny: |     if CORE.is_libretiny: | ||||||
|         CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") |         CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") | ||||||
|   | |||||||
| @@ -175,7 +175,7 @@ async def to_code(config): | |||||||
|                 not config.get(CONF_VERIFY_SSL), |                 not config.get(CONF_VERIFY_SSL), | ||||||
|             ) |             ) | ||||||
|         else: |         else: | ||||||
|             cg.add_library("WiFiClientSecure", None) |             cg.add_library("NetworkClientSecure", None) | ||||||
|             cg.add_library("HTTPClient", None) |             cg.add_library("HTTPClient", None) | ||||||
|     if CORE.is_esp8266: |     if CORE.is_esp8266: | ||||||
|         cg.add_library("ESP8266HTTPClient", None) |         cg.add_library("ESP8266HTTPClient", None) | ||||||
|   | |||||||
| @@ -239,7 +239,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> { | |||||||
|  |  | ||||||
|     std::string response_body; |     std::string response_body; | ||||||
|     if (this->capture_response_.value(x...)) { |     if (this->capture_response_.value(x...)) { | ||||||
|       ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |       RAMAllocator<uint8_t> allocator; | ||||||
|       uint8_t *buf = allocator.allocate(max_length); |       uint8_t *buf = allocator.allocate(max_length); | ||||||
|       if (buf != nullptr) { |       if (buf != nullptr) { | ||||||
|         size_t read_index = 0; |         size_t read_index = 0; | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
|  |  | ||||||
| #if defined(USE_ESP32) || defined(USE_RP2040) | #if defined(USE_ESP32) || defined(USE_RP2040) | ||||||
| #include <HTTPClient.h> | #include <HTTPClient.h> | ||||||
|  | #include <WiFiClient.h> | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| #include <ESP8266HTTPClient.h> | #include <ESP8266HTTPClient.h> | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ void HttpRequestUpdate::update_task(void *params) { | |||||||
|     UPDATE_RETURN; |     UPDATE_RETURN; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator; | ||||||
|   uint8_t *data = allocator.allocate(container->content_length); |   uint8_t *data = allocator.allocate(container->content_length); | ||||||
|   if (data == nullptr) { |   if (data == nullptr) { | ||||||
|     std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); |     std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); | ||||||
|   | |||||||
| @@ -22,8 +22,9 @@ import esphome.final_validate as fv | |||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| i2c_ns = cg.esphome_ns.namespace("i2c") | i2c_ns = cg.esphome_ns.namespace("i2c") | ||||||
| I2CBus = i2c_ns.class_("I2CBus") | I2CBus = i2c_ns.class_("I2CBus") | ||||||
| ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", I2CBus, cg.Component) | InternalI2CBus = i2c_ns.class_("InternalI2CBus", I2CBus) | ||||||
| IDFI2CBus = i2c_ns.class_("IDFI2CBus", I2CBus, cg.Component) | ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", InternalI2CBus, cg.Component) | ||||||
|  | IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component) | ||||||
| I2CDevice = i2c_ns.class_("I2CDevice") | I2CDevice = i2c_ns.class_("I2CDevice") | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -71,6 +72,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
| @coroutine_with_priority(1.0) | @coroutine_with_priority(1.0) | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     cg.add_global(i2c_ns.using) |     cg.add_global(i2c_ns.using) | ||||||
|  |     cg.add_define("USE_I2C") | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <cstdint> |  | ||||||
| #include <cstddef> | #include <cstddef> | ||||||
|  | #include <cstdint> | ||||||
| #include <utility> | #include <utility> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| @@ -108,5 +108,12 @@ class I2CBus { | |||||||
|   bool scan_{false};                                    ///< Should we scan ? Can be set in the yaml |   bool scan_{false};                                    ///< Should we scan ? Can be set in the yaml | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class InternalI2CBus : public I2CBus { | ||||||
|  |  public: | ||||||
|  |   /// @brief Returns the I2C port number. | ||||||
|  |   /// @return the port number of the internal I2C bus | ||||||
|  |   virtual int get_port() const = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace i2c | }  // namespace i2c | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
|  |  | ||||||
| #include "i2c_bus_arduino.h" | #include "i2c_bus_arduino.h" | ||||||
|  | #include <Arduino.h> | ||||||
|  | #include <cstring> | ||||||
| #include "esphome/core/application.h" | #include "esphome/core/application.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include <Arduino.h> |  | ||||||
| #include <cstring> |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace i2c { | namespace i2c { | ||||||
| @@ -23,6 +23,7 @@ void ArduinoI2CBus::setup() { | |||||||
|   } else { |   } else { | ||||||
|     wire_ = new TwoWire(next_bus_num);  // NOLINT(cppcoreguidelines-owning-memory) |     wire_ = new TwoWire(next_bus_num);  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|   } |   } | ||||||
|  |   this->port_ = next_bus_num; | ||||||
|   next_bus_num++; |   next_bus_num++; | ||||||
| #elif defined(USE_ESP8266) | #elif defined(USE_ESP8266) | ||||||
|   wire_ = new TwoWire();  // NOLINT(cppcoreguidelines-owning-memory) |   wire_ = new TwoWire();  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
| @@ -125,7 +126,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) | |||||||
|   size_t to_request = 0; |   size_t to_request = 0; | ||||||
|   for (size_t i = 0; i < cnt; i++) |   for (size_t i = 0; i < cnt; i++) | ||||||
|     to_request += buffers[i].len; |     to_request += buffers[i].len; | ||||||
|   size_t ret = wire_->requestFrom((int) address, (int) to_request, 1); |   size_t ret = wire_->requestFrom(address, to_request, true); | ||||||
|   if (ret != to_request) { |   if (ret != to_request) { | ||||||
|     ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret); |     ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret); | ||||||
|     return ERROR_TIMEOUT; |     return ERROR_TIMEOUT; | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
|  |  | ||||||
| #include "i2c_bus.h" |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include <Wire.h> | #include <Wire.h> | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "i2c_bus.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace i2c { | namespace i2c { | ||||||
| @@ -15,7 +15,7 @@ enum RecoveryCode { | |||||||
|   RECOVERY_COMPLETED, |   RECOVERY_COMPLETED, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ArduinoI2CBus : public I2CBus, public Component { | class ArduinoI2CBus : public InternalI2CBus, public Component { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| @@ -29,12 +29,15 @@ class ArduinoI2CBus : public I2CBus, public Component { | |||||||
|   void set_frequency(uint32_t frequency) { frequency_ = frequency; } |   void set_frequency(uint32_t frequency) { frequency_ = frequency; } | ||||||
|   void set_timeout(uint32_t timeout) { timeout_ = timeout; } |   void set_timeout(uint32_t timeout) { timeout_ = timeout; } | ||||||
|  |  | ||||||
|  |   int get_port() const override { return this->port_; } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   void recover_(); |   void recover_(); | ||||||
|   void set_pins_and_clock_(); |   void set_pins_and_clock_(); | ||||||
|   RecoveryCode recovery_result_; |   RecoveryCode recovery_result_; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   int8_t port_{-1}; | ||||||
|   TwoWire *wire_; |   TwoWire *wire_; | ||||||
|   uint8_t sda_pin_; |   uint8_t sda_pin_; | ||||||
|   uint8_t scl_pin_; |   uint8_t scl_pin_; | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
| #include "i2c_bus.h" |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include <driver/i2c.h> | #include <driver/i2c.h> | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "i2c_bus.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace i2c { | namespace i2c { | ||||||
| @@ -15,7 +15,7 @@ enum RecoveryCode { | |||||||
|   RECOVERY_COMPLETED, |   RECOVERY_COMPLETED, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class IDFI2CBus : public I2CBus, public Component { | class IDFI2CBus : public InternalI2CBus, public Component { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| @@ -31,6 +31,8 @@ class IDFI2CBus : public I2CBus, public Component { | |||||||
|   void set_frequency(uint32_t frequency) { frequency_ = frequency; } |   void set_frequency(uint32_t frequency) { frequency_ = frequency; } | ||||||
|   void set_timeout(uint32_t timeout) { timeout_ = timeout; } |   void set_timeout(uint32_t timeout) { timeout_ = timeout; } | ||||||
|  |  | ||||||
|  |   int get_port() const override { return static_cast<int>(this->port_); } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   void recover_(); |   void recover_(); | ||||||
|   RecoveryCode recovery_result_; |   RecoveryCode recovery_result_; | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ namespace i2s_audio { | |||||||
|  |  | ||||||
| static const char *const TAG = "i2s_audio"; | static const char *const TAG = "i2s_audio"; | ||||||
|  |  | ||||||
| #if defined(USE_ESP_IDF) && (ESP_IDF_VERSION_MAJOR >= 5) | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
| static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM;  // because IDF 5+ took this away :( | static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM;  // because IDF 5+ took this away :( | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -18,7 +18,7 @@ void I2SAudioComponent::setup() { | |||||||
|  |  | ||||||
|   static i2s_port_t next_port_num = I2S_NUM_0; |   static i2s_port_t next_port_num = I2S_NUM_0; | ||||||
|   if (next_port_num >= I2S_NUM_MAX) { |   if (next_port_num >= I2S_NUM_MAX) { | ||||||
|     ESP_LOGE(TAG, "Too many I2S Audio components"); |     ESP_LOGE(TAG, "Too many components"); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -114,7 +114,7 @@ async def to_code(config): | |||||||
|         cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) |         cg.add(var.set_external_dac_channels(2 if config[CONF_MODE] == "stereo" else 1)) | ||||||
|         cg.add(var.set_i2s_comm_fmt_lsb(config[CONF_I2S_COMM_FMT] == "lsb")) |         cg.add(var.set_i2s_comm_fmt_lsb(config[CONF_I2S_COMM_FMT] == "lsb")) | ||||||
|  |  | ||||||
|     cg.add_library("WiFiClientSecure", None) |     cg.add_library("NetworkClientSecure", None) | ||||||
|     cg.add_library("HTTPClient", None) |     cg.add_library("HTTPClient", None) | ||||||
|     cg.add_library("esphome/ESP32-audioI2S", "2.2.0") |     cg.add_library("esphome/ESP32-audioI2S", "2.3.0") | ||||||
|     cg.add_build_flag("-DAUDIO_NO_SD_FS") |     cg.add_build_flag("-DAUDIO_NO_SD_FS") | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ void I2SAudioMicrophone::setup() { | |||||||
| #if SOC_I2S_SUPPORTS_ADC | #if SOC_I2S_SUPPORTS_ADC | ||||||
|   if (this->adc_) { |   if (this->adc_) { | ||||||
|     if (this->parent_->get_port() != I2S_NUM_0) { |     if (this->parent_->get_port() != I2S_NUM_0) { | ||||||
|       ESP_LOGE(TAG, "Internal ADC only works on I2S0!"); |       ESP_LOGE(TAG, "Internal ADC only works on I2S0"); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| @@ -55,7 +55,7 @@ void I2SAudioMicrophone::setup() { | |||||||
|   { |   { | ||||||
|     if (this->pdm_) { |     if (this->pdm_) { | ||||||
|       if (this->parent_->get_port() != I2S_NUM_0) { |       if (this->parent_->get_port() != I2S_NUM_0) { | ||||||
|         ESP_LOGE(TAG, "PDM only works on I2S0!"); |         ESP_LOGE(TAG, "PDM only works on I2S0"); | ||||||
|         this->mark_failed(); |         this->mark_failed(); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
| @@ -64,14 +64,14 @@ void I2SAudioMicrophone::setup() { | |||||||
|  |  | ||||||
|   this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS); |   this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS); | ||||||
|   if (this->active_listeners_semaphore_ == nullptr) { |   if (this->active_listeners_semaphore_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Failed to create semaphore"); |     ESP_LOGE(TAG, "Creating semaphore failed"); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->event_group_ = xEventGroupCreate(); |   this->event_group_ = xEventGroupCreate(); | ||||||
|   if (this->event_group_ == nullptr) { |   if (this->event_group_ == nullptr) { | ||||||
|     ESP_LOGE(TAG, "Failed to create event group"); |     ESP_LOGE(TAG, "Creating event group failed"); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -79,6 +79,15 @@ void I2SAudioMicrophone::setup() { | |||||||
|   this->configure_stream_settings_(); |   this->configure_stream_settings_(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void I2SAudioMicrophone::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, | ||||||
|  |                 "Microphone:\n" | ||||||
|  |                 "  Pin: %d\n" | ||||||
|  |                 "  PDM: %s\n" | ||||||
|  |                 "  DC offset correction: %s", | ||||||
|  |                 static_cast<int8_t>(this->din_pin_), YESNO(this->pdm_), YESNO(this->correct_dc_offset_)); | ||||||
|  | } | ||||||
|  |  | ||||||
| void I2SAudioMicrophone::configure_stream_settings_() { | void I2SAudioMicrophone::configure_stream_settings_() { | ||||||
|   uint8_t channel_count = 1; |   uint8_t channel_count = 1; | ||||||
| #ifdef USE_I2S_LEGACY | #ifdef USE_I2S_LEGACY | ||||||
| @@ -127,6 +136,7 @@ bool I2SAudioMicrophone::start_driver_() { | |||||||
|   if (!this->parent_->try_lock()) { |   if (!this->parent_->try_lock()) { | ||||||
|     return false;  // Waiting for another i2s to return lock |     return false;  // Waiting for another i2s to return lock | ||||||
|   } |   } | ||||||
|  |   this->locked_driver_ = true; | ||||||
|   esp_err_t err; |   esp_err_t err; | ||||||
|  |  | ||||||
| #ifdef USE_I2S_LEGACY | #ifdef USE_I2S_LEGACY | ||||||
| @@ -151,7 +161,7 @@ bool I2SAudioMicrophone::start_driver_() { | |||||||
|     config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN); |     config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN); | ||||||
|     err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); |     err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); | ||||||
|     if (err != ESP_OK) { |     if (err != ESP_OK) { | ||||||
|       ESP_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err)); |       ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err)); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -174,7 +184,7 @@ bool I2SAudioMicrophone::start_driver_() { | |||||||
|  |  | ||||||
|     err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); |     err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr); | ||||||
|     if (err != ESP_OK) { |     if (err != ESP_OK) { | ||||||
|       ESP_LOGE(TAG, "Error installing I2S driver: %s", esp_err_to_name(err)); |       ESP_LOGE(TAG, "Error installing driver: %s", esp_err_to_name(err)); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -183,7 +193,7 @@ bool I2SAudioMicrophone::start_driver_() { | |||||||
|  |  | ||||||
|     err = i2s_set_pin(this->parent_->get_port(), &pin_config); |     err = i2s_set_pin(this->parent_->get_port(), &pin_config); | ||||||
|     if (err != ESP_OK) { |     if (err != ESP_OK) { | ||||||
|       ESP_LOGE(TAG, "Error setting I2S pin: %s", esp_err_to_name(err)); |       ESP_LOGE(TAG, "Error setting pin: %s", esp_err_to_name(err)); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -198,7 +208,7 @@ bool I2SAudioMicrophone::start_driver_() { | |||||||
|   /* Allocate a new RX channel and get the handle of this channel */ |   /* Allocate a new RX channel and get the handle of this channel */ | ||||||
|   err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_); |   err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err)); |     ESP_LOGE(TAG, "Error creating channel: %s", esp_err_to_name(err)); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -270,14 +280,14 @@ bool I2SAudioMicrophone::start_driver_() { | |||||||
|     err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg); |     err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg); | ||||||
|   } |   } | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err)); |     ESP_LOGE(TAG, "Error initializing channel: %s", esp_err_to_name(err)); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /* Before reading data, start the RX channel first */ |   /* Before reading data, start the RX channel first */ | ||||||
|   i2s_channel_enable(this->rx_handle_); |   i2s_channel_enable(this->rx_handle_); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err)); |     ESP_LOGE(TAG, "Enabling failed: %s", esp_err_to_name(err)); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
| @@ -304,31 +314,37 @@ void I2SAudioMicrophone::stop_driver_() { | |||||||
|   if (this->adc_) { |   if (this->adc_) { | ||||||
|     err = i2s_adc_disable(this->parent_->get_port()); |     err = i2s_adc_disable(this->parent_->get_port()); | ||||||
|     if (err != ESP_OK) { |     if (err != ESP_OK) { | ||||||
|       ESP_LOGW(TAG, "Error disabling ADC - it may not have started: %s", esp_err_to_name(err)); |       ESP_LOGW(TAG, "Error disabling ADC: %s", esp_err_to_name(err)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|   err = i2s_stop(this->parent_->get_port()); |   err = i2s_stop(this->parent_->get_port()); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err)); |     ESP_LOGW(TAG, "Error stopping: %s", esp_err_to_name(err)); | ||||||
|   } |   } | ||||||
|   err = i2s_driver_uninstall(this->parent_->get_port()); |   err = i2s_driver_uninstall(this->parent_->get_port()); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGW(TAG, "Error uninstalling I2S driver - it may not have started: %s", esp_err_to_name(err)); |     ESP_LOGW(TAG, "Error uninstalling driver: %s", esp_err_to_name(err)); | ||||||
|   } |   } | ||||||
| #else | #else | ||||||
|   /* Have to stop the channel before deleting it */ |   if (this->rx_handle_ != nullptr) { | ||||||
|   err = i2s_channel_disable(this->rx_handle_); |     /* Have to stop the channel before deleting it */ | ||||||
|   if (err != ESP_OK) { |     err = i2s_channel_disable(this->rx_handle_); | ||||||
|     ESP_LOGW(TAG, "Error stopping I2S microphone - it may not have started: %s", esp_err_to_name(err)); |     if (err != ESP_OK) { | ||||||
|   } |       ESP_LOGW(TAG, "Error stopping: %s", esp_err_to_name(err)); | ||||||
|   /* If the handle is not needed any more, delete it to release the channel resources */ |     } | ||||||
|   err = i2s_del_channel(this->rx_handle_); |     /* If the handle is not needed any more, delete it to release the channel resources */ | ||||||
|   if (err != ESP_OK) { |     err = i2s_del_channel(this->rx_handle_); | ||||||
|     ESP_LOGW(TAG, "Error deleting I2S channel - it may not have started: %s", esp_err_to_name(err)); |     if (err != ESP_OK) { | ||||||
|  |       ESP_LOGW(TAG, "Error deleting channel: %s", esp_err_to_name(err)); | ||||||
|  |     } | ||||||
|  |     this->rx_handle_ = nullptr; | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|   this->parent_->unlock(); |   if (this->locked_driver_) { | ||||||
|  |     this->parent_->unlock(); | ||||||
|  |     this->locked_driver_ = false; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void I2SAudioMicrophone::mic_task(void *params) { | void I2SAudioMicrophone::mic_task(void *params) { | ||||||
| @@ -400,7 +416,7 @@ size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_w | |||||||
|     // Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call |     // Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call | ||||||
|     if (!this->status_has_warning()) { |     if (!this->status_has_warning()) { | ||||||
|       // Avoid spamming the logs with the error message if its repeated |       // Avoid spamming the logs with the error message if its repeated | ||||||
|       ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); |       ESP_LOGW(TAG, "Read error: %s", esp_err_to_name(err)); | ||||||
|     } |     } | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return 0; |     return 0; | ||||||
| @@ -428,19 +444,19 @@ void I2SAudioMicrophone::loop() { | |||||||
|   uint32_t event_group_bits = xEventGroupGetBits(this->event_group_); |   uint32_t event_group_bits = xEventGroupGetBits(this->event_group_); | ||||||
|  |  | ||||||
|   if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) { |   if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) { | ||||||
|     ESP_LOGD(TAG, "Task started, attempting to allocate buffer"); |     ESP_LOGV(TAG, "Task started, attempting to allocate buffer"); | ||||||
|     xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING); |     xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) { |   if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) { | ||||||
|     ESP_LOGD(TAG, "Task is running and reading data"); |     ESP_LOGV(TAG, "Task is running and reading data"); | ||||||
|  |  | ||||||
|     xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING); |     xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING); | ||||||
|     this->state_ = microphone::STATE_RUNNING; |     this->state_ = microphone::STATE_RUNNING; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) { |   if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) { | ||||||
|     ESP_LOGD(TAG, "Task finished, freeing resources and uninstalling I2S driver"); |     ESP_LOGV(TAG, "Task finished, freeing resources and uninstalling driver"); | ||||||
|  |  | ||||||
|     vTaskDelete(this->task_handle_); |     vTaskDelete(this->task_handle_); | ||||||
|     this->task_handle_ = nullptr; |     this->task_handle_ = nullptr; | ||||||
| @@ -470,7 +486,8 @@ void I2SAudioMicrophone::loop() { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (!this->start_driver_()) { |       if (!this->start_driver_()) { | ||||||
|         this->status_momentary_error("I2S driver failed to start, unloading it and attempting again in 1 second", 1000); |         ESP_LOGE(TAG, "Driver failed to start; retrying in 1 second"); | ||||||
|  |         this->status_momentary_error("driver_fail", 1000); | ||||||
|         this->stop_driver_();  // Stop/frees whatever possibly started |         this->stop_driver_();  // Stop/frees whatever possibly started | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
| @@ -480,7 +497,8 @@ void I2SAudioMicrophone::loop() { | |||||||
|                     &this->task_handle_); |                     &this->task_handle_); | ||||||
|  |  | ||||||
|         if (this->task_handle_ == nullptr) { |         if (this->task_handle_ == nullptr) { | ||||||
|           this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000); |           ESP_LOGE(TAG, "Task failed to start, retrying in 1 second"); | ||||||
|  |           this->status_momentary_error("task_fail", 1000); | ||||||
|           this->stop_driver_();  // Stops the driver to return the lock; will be reloaded in next attempt |           this->stop_driver_();  // Stops the driver to return the lock; will be reloaded in next attempt | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ namespace i2s_audio { | |||||||
| class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, public Component { | class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, public Component { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|   void start() override; |   void start() override; | ||||||
|   void stop() override; |   void stop() override; | ||||||
|  |  | ||||||
| @@ -80,6 +81,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | |||||||
|   bool pdm_{false}; |   bool pdm_{false}; | ||||||
|  |  | ||||||
|   bool correct_dc_offset_; |   bool correct_dc_offset_; | ||||||
|  |   bool locked_driver_{false}; | ||||||
|   int32_t dc_offset_{0}; |   int32_t dc_offset_{0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -110,29 +110,48 @@ void I2SAudioSpeaker::setup() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void I2SAudioSpeaker::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, | ||||||
|  |                 "Speaker:\n" | ||||||
|  |                 "  Pin: %d\n" | ||||||
|  |                 "  Buffer duration: %" PRIu32, | ||||||
|  |                 static_cast<int8_t>(this->dout_pin_), this->buffer_duration_ms_); | ||||||
|  |   if (this->timeout_.has_value()) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Timeout: %" PRIu32 " ms", this->timeout_.value()); | ||||||
|  |   } | ||||||
|  | #ifdef USE_I2S_LEGACY | ||||||
|  | #if SOC_I2S_SUPPORTS_DAC | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Internal DAC mode: %d", static_cast<int8_t>(this->internal_dac_mode_)); | ||||||
|  | #endif | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Communication format: %d", static_cast<int8_t>(this->i2s_comm_fmt_)); | ||||||
|  | #else | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Communication format: %s", this->i2s_comm_fmt_.c_str()); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
| void I2SAudioSpeaker::loop() { | void I2SAudioSpeaker::loop() { | ||||||
|   uint32_t event_group_bits = xEventGroupGetBits(this->event_group_); |   uint32_t event_group_bits = xEventGroupGetBits(this->event_group_); | ||||||
|  |  | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::STATE_STARTING) { |   if (event_group_bits & SpeakerEventGroupBits::STATE_STARTING) { | ||||||
|     ESP_LOGD(TAG, "Starting Speaker"); |     ESP_LOGD(TAG, "Starting"); | ||||||
|     this->state_ = speaker::STATE_STARTING; |     this->state_ = speaker::STATE_STARTING; | ||||||
|     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STARTING); |     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STARTING); | ||||||
|   } |   } | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::STATE_RUNNING) { |   if (event_group_bits & SpeakerEventGroupBits::STATE_RUNNING) { | ||||||
|     ESP_LOGD(TAG, "Started Speaker"); |     ESP_LOGD(TAG, "Started"); | ||||||
|     this->state_ = speaker::STATE_RUNNING; |     this->state_ = speaker::STATE_RUNNING; | ||||||
|     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_RUNNING); |     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_RUNNING); | ||||||
|     this->status_clear_warning(); |     this->status_clear_warning(); | ||||||
|     this->status_clear_error(); |     this->status_clear_error(); | ||||||
|   } |   } | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPING) { |   if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPING) { | ||||||
|     ESP_LOGD(TAG, "Stopping Speaker"); |     ESP_LOGD(TAG, "Stopping"); | ||||||
|     this->state_ = speaker::STATE_STOPPING; |     this->state_ = speaker::STATE_STOPPING; | ||||||
|     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPING); |     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPING); | ||||||
|   } |   } | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPED) { |   if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPED) { | ||||||
|     if (!this->task_created_) { |     if (!this->task_created_) { | ||||||
|       ESP_LOGD(TAG, "Stopped Speaker"); |       ESP_LOGD(TAG, "Stopped"); | ||||||
|       this->state_ = speaker::STATE_STOPPED; |       this->state_ = speaker::STATE_STOPPED; | ||||||
|       xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ALL_BITS); |       xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ALL_BITS); | ||||||
|       this->speaker_task_handle_ = nullptr; |       this->speaker_task_handle_ = nullptr; | ||||||
| @@ -140,20 +159,19 @@ void I2SAudioSpeaker::loop() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) { |   if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) { | ||||||
|     this->status_set_error("Failed to start speaker task"); |     this->status_set_error("Failed to start task"); | ||||||
|     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); |     xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { |   if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) { | ||||||
|     uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; |     uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS; | ||||||
|     ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); |     ESP_LOGW(TAG, "Writing failed: %s", esp_err_to_name(err_bit_to_esp_err(error_bits))); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) { |   if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) { | ||||||
|     this->status_set_error("Failed to adjust I2S bus to match the incoming audio"); |     this->status_set_error("Failed to adjust bus to match incoming audio"); | ||||||
|     ESP_LOGE(TAG, |     ESP_LOGE(TAG, "Incompatible audio format: sample rate = %" PRIu32 ", channels = %u, bits per sample = %u", | ||||||
|              "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8, |  | ||||||
|              this->audio_stream_info_.get_sample_rate(), this->audio_stream_info_.get_channels(), |              this->audio_stream_info_.get_sample_rate(), this->audio_stream_info_.get_channels(), | ||||||
|              this->audio_stream_info_.get_bits_per_sample()); |              this->audio_stream_info_.get_bits_per_sample()); | ||||||
|   } |   } | ||||||
| @@ -202,7 +220,7 @@ void I2SAudioSpeaker::set_mute_state(bool mute_state) { | |||||||
|  |  | ||||||
| size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) { | size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) { | ||||||
|   if (this->is_failed()) { |   if (this->is_failed()) { | ||||||
|     ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup"); |     ESP_LOGE(TAG, "Setup failed; cannot play audio"); | ||||||
|     return 0; |     return 0; | ||||||
|   } |   } | ||||||
|   if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) { |   if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) { | ||||||
| @@ -466,7 +484,7 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) { | |||||||
| esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size) { | esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size) { | ||||||
|   if (this->data_buffer_ == nullptr) { |   if (this->data_buffer_ == nullptr) { | ||||||
|     // Allocate data buffer for temporarily storing audio from the ring buffer before writing to the I2S bus |     // Allocate data buffer for temporarily storing audio from the ring buffer before writing to the I2S bus | ||||||
|     ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |     RAMAllocator<uint8_t> allocator; | ||||||
|     this->data_buffer_ = allocator.allocate(data_buffer_size); |     this->data_buffer_ = allocator.allocate(data_buffer_size); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -680,7 +698,7 @@ void I2SAudioSpeaker::delete_task_(size_t buffer_size) { | |||||||
|   this->audio_ring_buffer_.reset();  // Releases ownership of the shared_ptr |   this->audio_ring_buffer_.reset();  // Releases ownership of the shared_ptr | ||||||
|  |  | ||||||
|   if (this->data_buffer_ != nullptr) { |   if (this->data_buffer_ != nullptr) { | ||||||
|     ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |     RAMAllocator<uint8_t> allocator; | ||||||
|     allocator.deallocate(this->data_buffer_, buffer_size); |     allocator.deallocate(this->data_buffer_, buffer_size); | ||||||
|     this->data_buffer_ = nullptr; |     this->data_buffer_ = nullptr; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp | |||||||
|   float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } |   float get_setup_priority() const override { return esphome::setup_priority::PROCESSOR; } | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|   void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; } |   void set_buffer_duration(uint32_t buffer_duration_ms) { this->buffer_duration_ms_ = buffer_duration_ms; } | ||||||
|   | |||||||
| @@ -129,6 +129,13 @@ void INA219Component::setup() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void INA219Component::on_powerdown() { | ||||||
|  |   // Mode = 0 -> power down | ||||||
|  |   if (!this->write_byte_16(INA219_REGISTER_CONFIG, 0)) { | ||||||
|  |     ESP_LOGE(TAG, "powerdown error"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| void INA219Component::dump_config() { | void INA219Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "INA219:"); |   ESP_LOGCONFIG(TAG, "INA219:"); | ||||||
|   LOG_I2C_DEVICE(this); |   LOG_I2C_DEVICE(this); | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ class INA219Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|   void update() override; |   void update() override; | ||||||
|  |   void on_powerdown() override; | ||||||
|  |  | ||||||
|   void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; } |   void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; } | ||||||
|   void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; } |   void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; } | ||||||
|   | |||||||
| @@ -57,8 +57,8 @@ void Inkplate6::setup() { | |||||||
|  * Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed. |  * Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed. | ||||||
|  */ |  */ | ||||||
| void Inkplate6::initialize_() { | void Inkplate6::initialize_() { | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   RAMAllocator<uint8_t> allocator; | ||||||
|   ExternalRAMAllocator<uint32_t> allocator32(ExternalRAMAllocator<uint32_t>::ALLOW_FAILURE); |   RAMAllocator<uint32_t> allocator32; | ||||||
|   uint32_t buffer_size = this->get_buffer_length_(); |   uint32_t buffer_size = this->get_buffer_length_(); | ||||||
|   if (buffer_size == 0) |   if (buffer_size == 0) | ||||||
|     return; |     return; | ||||||
|   | |||||||
| @@ -19,9 +19,8 @@ void KMeterISOComponent::setup() { | |||||||
|  |  | ||||||
|   // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries |   // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries | ||||||
|   // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. |   // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. | ||||||
|   if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { |   if (this->is_failed()) { | ||||||
|     this->component_state_ &= ~COMPONENT_STATE_MASK; |     this->reset_to_construction_state(); | ||||||
|     this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   auto err = this->bus_->writev(this->address_, nullptr, 0); |   auto err = this->bus_->writev(this->address_, nullptr, 0); | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ from esphome.components import number | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_MOVE_THRESHOLD, | ||||||
|  |     CONF_STILL_THRESHOLD, | ||||||
|     CONF_TIMEOUT, |     CONF_TIMEOUT, | ||||||
|     DEVICE_CLASS_DISTANCE, |     DEVICE_CLASS_DISTANCE, | ||||||
|     DEVICE_CLASS_ILLUMINANCE, |     DEVICE_CLASS_ILLUMINANCE, | ||||||
| @@ -24,8 +26,6 @@ MaxDistanceTimeoutNumber = ld2410_ns.class_("MaxDistanceTimeoutNumber", number.N | |||||||
| CONF_MAX_MOVE_DISTANCE_GATE = "max_move_distance_gate" | CONF_MAX_MOVE_DISTANCE_GATE = "max_move_distance_gate" | ||||||
| CONF_MAX_STILL_DISTANCE_GATE = "max_still_distance_gate" | CONF_MAX_STILL_DISTANCE_GATE = "max_still_distance_gate" | ||||||
| CONF_LIGHT_THRESHOLD = "light_threshold" | CONF_LIGHT_THRESHOLD = "light_threshold" | ||||||
| CONF_STILL_THRESHOLD = "still_threshold" |  | ||||||
| CONF_MOVE_THRESHOLD = "move_threshold" |  | ||||||
|  |  | ||||||
| TIMEOUT_GROUP = "timeout" | TIMEOUT_GROUP = "timeout" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ from esphome.components import sensor | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_LIGHT, |     CONF_LIGHT, | ||||||
|  |     CONF_MOVING_DISTANCE, | ||||||
|     DEVICE_CLASS_DISTANCE, |     DEVICE_CLASS_DISTANCE, | ||||||
|     DEVICE_CLASS_ILLUMINANCE, |     DEVICE_CLASS_ILLUMINANCE, | ||||||
|     ENTITY_CATEGORY_DIAGNOSTIC, |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
| @@ -17,7 +18,6 @@ from esphome.const import ( | |||||||
| from . import CONF_LD2410_ID, LD2410Component | from . import CONF_LD2410_ID, LD2410Component | ||||||
|  |  | ||||||
| DEPENDENCIES = ["ld2410"] | DEPENDENCIES = ["ld2410"] | ||||||
| CONF_MOVING_DISTANCE = "moving_distance" |  | ||||||
| CONF_STILL_DISTANCE = "still_distance" | CONF_STILL_DISTANCE = "still_distance" | ||||||
| CONF_MOVING_ENERGY = "moving_energy" | CONF_MOVING_ENERGY = "moving_energy" | ||||||
| CONF_STILL_ENERGY = "still_energy" | CONF_STILL_ENERGY = "still_energy" | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import esphome.codegen as cg | |||||||
| from esphome.components import switch | from esphome.components import switch | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_BLUETOOTH, | ||||||
|     DEVICE_CLASS_SWITCH, |     DEVICE_CLASS_SWITCH, | ||||||
|     ENTITY_CATEGORY_CONFIG, |     ENTITY_CATEGORY_CONFIG, | ||||||
|     ICON_BLUETOOTH, |     ICON_BLUETOOTH, | ||||||
| @@ -14,7 +15,6 @@ BluetoothSwitch = ld2410_ns.class_("BluetoothSwitch", switch.Switch) | |||||||
| EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch) | EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch) | ||||||
|  |  | ||||||
| CONF_ENGINEERING_MODE = "engineering_mode" | CONF_ENGINEERING_MODE = "engineering_mode" | ||||||
| CONF_BLUETOOTH = "bluetooth" |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = { | CONFIG_SCHEMA = { | ||||||
|     cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), |     cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ from esphome.components import number | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_MOVE_THRESHOLD, | ||||||
|  |     CONF_STILL_THRESHOLD, | ||||||
|     DEVICE_CLASS_DISTANCE, |     DEVICE_CLASS_DISTANCE, | ||||||
|     ENTITY_CATEGORY_CONFIG, |     ENTITY_CATEGORY_CONFIG, | ||||||
|     ICON_MOTION_SENSOR, |     ICON_MOTION_SENSOR, | ||||||
| @@ -31,8 +33,6 @@ LD2420StillThresholdNumbers = ld2420_ns.class_( | |||||||
| ) | ) | ||||||
| CONF_MIN_GATE_DISTANCE = "min_gate_distance" | CONF_MIN_GATE_DISTANCE = "min_gate_distance" | ||||||
| CONF_MAX_GATE_DISTANCE = "max_gate_distance" | CONF_MAX_GATE_DISTANCE = "max_gate_distance" | ||||||
| CONF_STILL_THRESHOLD = "still_threshold" |  | ||||||
| CONF_MOVE_THRESHOLD = "move_threshold" |  | ||||||
| CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity" | CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity" | ||||||
| CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity" | CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity" | ||||||
| CONF_GATE_SELECT = "gate_select" | CONF_GATE_SELECT = "gate_select" | ||||||
|   | |||||||
| @@ -1,13 +1,17 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import CONF_ID, DEVICE_CLASS_DISTANCE, UNIT_CENTIMETER | from esphome.const import ( | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_MOVING_DISTANCE, | ||||||
|  |     DEVICE_CLASS_DISTANCE, | ||||||
|  |     UNIT_CENTIMETER, | ||||||
|  | ) | ||||||
|  |  | ||||||
| from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns | from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns | ||||||
|  |  | ||||||
| LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component) | LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component) | ||||||
|  |  | ||||||
| CONF_MOVING_DISTANCE = "moving_distance" |  | ||||||
| CONF_GATE_ENERGY = "gate_energy" | CONF_GATE_ENERGY = "gate_energy" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import esphome.codegen as cg | |||||||
| from esphome.components import switch | from esphome.components import switch | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_BLUETOOTH, | ||||||
|     DEVICE_CLASS_SWITCH, |     DEVICE_CLASS_SWITCH, | ||||||
|     ENTITY_CATEGORY_CONFIG, |     ENTITY_CATEGORY_CONFIG, | ||||||
|     ICON_BLUETOOTH, |     ICON_BLUETOOTH, | ||||||
| @@ -13,7 +14,6 @@ from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | |||||||
| BluetoothSwitch = ld2450_ns.class_("BluetoothSwitch", switch.Switch) | BluetoothSwitch = ld2450_ns.class_("BluetoothSwitch", switch.Switch) | ||||||
| MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch) | MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch) | ||||||
|  |  | ||||||
| CONF_BLUETOOTH = "bluetooth" |  | ||||||
| CONF_MULTI_TARGET = "multi_target" | CONF_MULTI_TARGET = "multi_target" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = { | CONFIG_SCHEMA = { | ||||||
|   | |||||||
| @@ -3,28 +3,16 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
| #include <esp32-hal-ledc.h> |  | ||||||
| #endif |  | ||||||
| #include <driver/ledc.h> | #include <driver/ledc.h> | ||||||
|  |  | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
|  |  | ||||||
| #define CLOCK_FREQUENCY 80e6f | #define CLOCK_FREQUENCY 80e6f | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
| #ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK |  | ||||||
| #undef CLOCK_FREQUENCY |  | ||||||
| // starting with ESP32 Arduino 2.0.2, the 40MHz crystal is used as clock by default if supported |  | ||||||
| #define CLOCK_FREQUENCY 40e6f |  | ||||||
| #endif |  | ||||||
| #else |  | ||||||
| #ifdef SOC_LEDC_SUPPORT_APB_CLOCK | #ifdef SOC_LEDC_SUPPORT_APB_CLOCK | ||||||
| #define DEFAULT_CLK LEDC_USE_APB_CLK | #define DEFAULT_CLK LEDC_USE_APB_CLK | ||||||
| #else | #else | ||||||
| #define DEFAULT_CLK LEDC_AUTO_CLK | #define DEFAULT_CLK LEDC_AUTO_CLK | ||||||
| #endif | #endif | ||||||
| #endif |  | ||||||
|  |  | ||||||
| static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5; | static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5; | ||||||
|  |  | ||||||
| @@ -34,7 +22,6 @@ namespace ledc { | |||||||
| static const char *const TAG = "ledc.output"; | static const char *const TAG = "ledc.output"; | ||||||
|  |  | ||||||
| static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1; | static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1; | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
| #if SOC_LEDC_SUPPORT_HS_MODE | #if SOC_LEDC_SUPPORT_HS_MODE | ||||||
| // Only ESP32 has LEDC_HIGH_SPEED_MODE | // Only ESP32 has LEDC_HIGH_SPEED_MODE | ||||||
| inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; } | inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; } | ||||||
| @@ -44,7 +31,6 @@ inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_H | |||||||
| // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview | // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview | ||||||
| inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; } | inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; } | ||||||
| #endif | #endif | ||||||
| #endif |  | ||||||
|  |  | ||||||
| float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { | float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { | ||||||
|   return static_cast<float>(CLOCK_FREQUENCY) / static_cast<float>(1 << bit_depth); |   return static_cast<float>(CLOCK_FREQUENCY) / static_cast<float>(1 << bit_depth); | ||||||
| @@ -68,7 +54,6 @@ optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) { | |||||||
|   return {}; |   return {}; | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
| esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_channel_t chan_num, | esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_channel_t chan_num, | ||||||
|                                     uint8_t channel, uint8_t &bit_depth, float frequency) { |                                     uint8_t channel, uint8_t &bit_depth, float frequency) { | ||||||
|   bit_depth = *ledc_bit_depth_for_frequency(frequency); |   bit_depth = *ledc_bit_depth_for_frequency(frequency); | ||||||
| @@ -98,13 +83,10 @@ esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_n | |||||||
|  |  | ||||||
|   return init_result; |   return init_result; | ||||||
| } | } | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
| constexpr int ledc_angle_to_htop(float angle, uint8_t bit_depth) { | constexpr int ledc_angle_to_htop(float angle, uint8_t bit_depth) { | ||||||
|   return static_cast<int>(angle * ((1U << bit_depth) - 1) / 360.0f); |   return static_cast<int>(angle * ((1U << bit_depth) - 1) / 360.0f); | ||||||
| } | } | ||||||
| #endif  // USE_ESP_IDF |  | ||||||
|  |  | ||||||
| void LEDCOutput::write_state(float state) { | void LEDCOutput::write_state(float state) { | ||||||
|   if (!this->initialized_) { |   if (!this->initialized_) { | ||||||
| @@ -120,10 +102,6 @@ void LEDCOutput::write_state(float state) { | |||||||
|   const float duty_rounded = roundf(state * max_duty); |   const float duty_rounded = roundf(state * max_duty); | ||||||
|   auto duty = static_cast<uint32_t>(duty_rounded); |   auto duty = static_cast<uint32_t>(duty_rounded); | ||||||
|   ESP_LOGV(TAG, "Setting duty: %" PRIu32 " on channel %u", duty, this->channel_); |   ESP_LOGV(TAG, "Setting duty: %" PRIu32 " on channel %u", duty, this->channel_); | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|   ledcWrite(this->channel_, duty); |  | ||||||
| #endif |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
|   auto speed_mode = get_speed_mode(this->channel_); |   auto speed_mode = get_speed_mode(this->channel_); | ||||||
|   auto chan_num = static_cast<ledc_channel_t>(this->channel_ % 8); |   auto chan_num = static_cast<ledc_channel_t>(this->channel_ % 8); | ||||||
|   int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); |   int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_); | ||||||
| @@ -135,18 +113,10 @@ void LEDCOutput::write_state(float state) { | |||||||
|     ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint); |     ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint); | ||||||
|     ledc_update_duty(speed_mode, chan_num); |     ledc_update_duty(speed_mode, chan_num); | ||||||
|   } |   } | ||||||
| #endif |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LEDCOutput::setup() { | void LEDCOutput::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Running setup"); |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|   this->update_frequency(this->frequency_); |  | ||||||
|   this->turn_off(); |  | ||||||
|   // Attach pin after setting default value |  | ||||||
|   ledcAttachPin(this->pin_->get_pin(), this->channel_); |  | ||||||
| #endif |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
|   auto speed_mode = get_speed_mode(this->channel_); |   auto speed_mode = get_speed_mode(this->channel_); | ||||||
|   auto timer_num = static_cast<ledc_timer_t>((this->channel_ % 8) / 2); |   auto timer_num = static_cast<ledc_timer_t>((this->channel_ % 8) / 2); | ||||||
|   auto chan_num = static_cast<ledc_channel_t>(this->channel_ % 8); |   auto chan_num = static_cast<ledc_channel_t>(this->channel_ % 8); | ||||||
| @@ -175,7 +145,6 @@ void LEDCOutput::setup() { | |||||||
|   ledc_channel_config(&chan_conf); |   ledc_channel_config(&chan_conf); | ||||||
|   this->initialized_ = true; |   this->initialized_ = true; | ||||||
|   this->status_clear_error(); |   this->status_clear_error(); | ||||||
| #endif |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LEDCOutput::dump_config() { | void LEDCOutput::dump_config() { | ||||||
| @@ -208,38 +177,7 @@ void LEDCOutput::update_frequency(float frequency) { | |||||||
|   } |   } | ||||||
|   this->bit_depth_ = bit_depth_opt.value_or(8); |   this->bit_depth_ = bit_depth_opt.value_or(8); | ||||||
|   this->frequency_ = frequency; |   this->frequency_ = frequency; | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|   ESP_LOGV(TAG, "Using Arduino API - Trying to define channel, frequency and bit depth"); |  | ||||||
|   u_int32_t configured_frequency = 0; |  | ||||||
|  |  | ||||||
|   // Configure LEDC channel, frequency and bit depth with fallback |  | ||||||
|   int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX; |  | ||||||
|   while (attempt_count_max > 0 && configured_frequency == 0) { |  | ||||||
|     ESP_LOGV(TAG, "Initializing channel %u with frequency %.1f and bit depth of %u", this->channel_, this->frequency_, |  | ||||||
|              this->bit_depth_); |  | ||||||
|     configured_frequency = ledcSetup(this->channel_, frequency, this->bit_depth_); |  | ||||||
|     if (configured_frequency != 0) { |  | ||||||
|       this->initialized_ = true; |  | ||||||
|       this->status_clear_error(); |  | ||||||
|       ESP_LOGV(TAG, "Configured frequency: %u with bit depth: %u", configured_frequency, this->bit_depth_); |  | ||||||
|     } else { |  | ||||||
|       ESP_LOGW(TAG, "Unable to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_, |  | ||||||
|                this->frequency_, this->bit_depth_); |  | ||||||
|       // try again with a lower bit depth |  | ||||||
|       this->bit_depth_--; |  | ||||||
|     } |  | ||||||
|     attempt_count_max--; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (configured_frequency == 0) { |  | ||||||
|     ESP_LOGE(TAG, "Permanently failed to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_, |  | ||||||
|              this->frequency_, this->bit_depth_); |  | ||||||
|     this->status_set_error(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
|   if (!this->initialized_) { |   if (!this->initialized_) { | ||||||
|     ESP_LOGW(TAG, "Not yet initialized"); |     ESP_LOGW(TAG, "Not yet initialized"); | ||||||
|     return; |     return; | ||||||
| @@ -259,7 +197,7 @@ void LEDCOutput::update_frequency(float frequency) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->status_clear_error(); |   this->status_clear_error(); | ||||||
| #endif |  | ||||||
|   // re-apply duty |   // re-apply duty | ||||||
|   this->write_state(this->duty_); |   this->write_state(this->duty_); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -173,9 +173,9 @@ def _notify_old_style(config): | |||||||
|  |  | ||||||
| # The dev and latest branches will be at *least* this version, which is what matters. | # The dev and latest branches will be at *least* this version, which is what matters. | ||||||
| ARDUINO_VERSIONS = { | ARDUINO_VERSIONS = { | ||||||
|     "dev": (cv.Version(1, 7, 0), "https://github.com/libretiny-eu/libretiny.git"), |     "dev": (cv.Version(1, 9, 1), "https://github.com/libretiny-eu/libretiny.git"), | ||||||
|     "latest": (cv.Version(1, 7, 0), "libretiny"), |     "latest": (cv.Version(1, 9, 1), "libretiny"), | ||||||
|     "recommended": (cv.Version(1, 7, 0), None), |     "recommended": (cv.Version(1, 9, 1), None), | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -264,6 +264,7 @@ async def component_to_code(config): | |||||||
|     # force using arduino framework |     # force using arduino framework | ||||||
|     cg.add_platformio_option("framework", "arduino") |     cg.add_platformio_option("framework", "arduino") | ||||||
|     cg.add_build_flag("-DUSE_ARDUINO") |     cg.add_build_flag("-DUSE_ARDUINO") | ||||||
|  |     cg.set_cpp_standard("gnu++17") | ||||||
|  |  | ||||||
|     # disable library compatibility checks |     # disable library compatibility checks | ||||||
|     cg.add_platformio_option("lib_ldf_mode", "off") |     cg.add_platformio_option("lib_ldf_mode", "off") | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ namespace light { | |||||||
|  |  | ||||||
| class LightOutput; | class LightOutput; | ||||||
|  |  | ||||||
| enum LightRestoreMode { | enum LightRestoreMode : uint8_t { | ||||||
|   LIGHT_RESTORE_DEFAULT_OFF, |   LIGHT_RESTORE_DEFAULT_OFF, | ||||||
|   LIGHT_RESTORE_DEFAULT_ON, |   LIGHT_RESTORE_DEFAULT_ON, | ||||||
|   LIGHT_ALWAYS_OFF, |   LIGHT_ALWAYS_OFF, | ||||||
| @@ -212,12 +212,18 @@ class LightState : public EntityBase, public Component { | |||||||
|  |  | ||||||
|   /// Store the output to allow effects to have more access. |   /// Store the output to allow effects to have more access. | ||||||
|   LightOutput *output_; |   LightOutput *output_; | ||||||
|   /// Value for storing the index of the currently active effect. 0 if no effect is active |  | ||||||
|   uint32_t active_effect_index_{}; |  | ||||||
|   /// The currently active transformer for this light (transition/flash). |   /// The currently active transformer for this light (transition/flash). | ||||||
|   std::unique_ptr<LightTransformer> transformer_{nullptr}; |   std::unique_ptr<LightTransformer> transformer_{nullptr}; | ||||||
|   /// Whether the light value should be written in the next cycle. |   /// List of effects for this light. | ||||||
|   bool next_write_{true}; |   std::vector<LightEffect *> effects_; | ||||||
|  |   /// Value for storing the index of the currently active effect. 0 if no effect is active | ||||||
|  |   uint32_t active_effect_index_{}; | ||||||
|  |   /// Default transition length for all transitions in ms. | ||||||
|  |   uint32_t default_transition_length_{}; | ||||||
|  |   /// Transition length to use for flash transitions. | ||||||
|  |   uint32_t flash_transition_length_{}; | ||||||
|  |   /// Gamma correction factor for the light. | ||||||
|  |   float gamma_correct_{}; | ||||||
|  |  | ||||||
|   /// Object used to store the persisted values of the light. |   /// Object used to store the persisted values of the light. | ||||||
|   ESPPreferenceObject rtc_; |   ESPPreferenceObject rtc_; | ||||||
| @@ -236,19 +242,13 @@ class LightState : public EntityBase, public Component { | |||||||
|    */ |    */ | ||||||
|   CallbackManager<void()> target_state_reached_callback_{}; |   CallbackManager<void()> target_state_reached_callback_{}; | ||||||
|  |  | ||||||
|   /// Default transition length for all transitions in ms. |  | ||||||
|   uint32_t default_transition_length_{}; |  | ||||||
|   /// Transition length to use for flash transitions. |  | ||||||
|   uint32_t flash_transition_length_{}; |  | ||||||
|   /// Gamma correction factor for the light. |  | ||||||
|   float gamma_correct_{}; |  | ||||||
|   /// Restore mode of the light. |  | ||||||
|   LightRestoreMode restore_mode_; |  | ||||||
|   /// Initial state of the light. |   /// Initial state of the light. | ||||||
|   optional<LightStateRTCState> initial_state_{}; |   optional<LightStateRTCState> initial_state_{}; | ||||||
|   /// List of effects for this light. |  | ||||||
|   std::vector<LightEffect *> effects_; |  | ||||||
|  |  | ||||||
|  |   /// Restore mode of the light. | ||||||
|  |   LightRestoreMode restore_mode_; | ||||||
|  |   /// Whether the light value should be written in the next cycle. | ||||||
|  |   bool next_write_{true}; | ||||||
|   // for effects, true if a transformer (transition) is active. |   // for effects, true if a transformer (transition) is active. | ||||||
|   bool is_transformer_active_ = false; |   bool is_transformer_active_ = false; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -324,7 +324,10 @@ async def to_code(config): | |||||||
|     if CORE.using_arduino: |     if CORE.using_arduino: | ||||||
|         if config[CONF_HARDWARE_UART] == USB_CDC: |         if config[CONF_HARDWARE_UART] == USB_CDC: | ||||||
|             cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") |             cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1") | ||||||
|             if CORE.is_esp32 and get_esp32_variant() == VARIANT_ESP32C3: |             if CORE.is_esp32 and get_esp32_variant() in ( | ||||||
|  |                 VARIANT_ESP32C3, | ||||||
|  |                 VARIANT_ESP32C6, | ||||||
|  |             ): | ||||||
|                 cg.add_build_flag("-DARDUINO_USB_MODE=1") |                 cg.add_build_flag("-DARDUINO_USB_MODE=1") | ||||||
|  |  | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|   | |||||||
| @@ -116,7 +116,7 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr | |||||||
|   if (this->baud_rate_ > 0) { |   if (this->baud_rate_ > 0) { | ||||||
|     this->write_msg_(this->tx_buffer_ + msg_start); |     this->write_msg_(this->tx_buffer_ + msg_start); | ||||||
|   } |   } | ||||||
|   this->call_log_callbacks_(level, tag, this->tx_buffer_ + msg_start); |   this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start); | ||||||
|  |  | ||||||
|   global_recursion_guard_ = false; |   global_recursion_guard_ = false; | ||||||
| } | } | ||||||
| @@ -129,19 +129,6 @@ inline int Logger::level_for(const char *tag) { | |||||||
|   return this->current_level_; |   return this->current_level_; | ||||||
| } | } | ||||||
|  |  | ||||||
| void HOT Logger::call_log_callbacks_(int level, const char *tag, const char *msg) { |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
|   // Suppress network-logging if memory constrained |  | ||||||
|   // In some configurations (eg BLE enabled) there may be some transient |  | ||||||
|   // memory exhaustion, and trying to log when OOM can lead to a crash. Skipping |  | ||||||
|   // here usually allows the stack to recover instead. |  | ||||||
|   // See issue #1234 for analysis. |  | ||||||
|   if (xPortGetFreeHeapSize() < 2048) |  | ||||||
|     return; |  | ||||||
| #endif |  | ||||||
|   this->log_callback_.call(level, tag, msg); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) { | Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) { | ||||||
|   // add 1 to buffer size for null terminator |   // add 1 to buffer size for null terminator | ||||||
|   this->tx_buffer_ = new char[this->tx_buffer_size_ + 1];  // NOLINT |   this->tx_buffer_ = new char[this->tx_buffer_size_ + 1];  // NOLINT | ||||||
| @@ -189,7 +176,7 @@ void Logger::loop() { | |||||||
|                                   this->tx_buffer_size_); |                                   this->tx_buffer_size_); | ||||||
|       this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); |       this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); | ||||||
|       this->tx_buffer_[this->tx_buffer_at_] = '\0'; |       this->tx_buffer_[this->tx_buffer_at_] = '\0'; | ||||||
|       this->call_log_callbacks_(message->level, message->tag, this->tx_buffer_); |       this->log_callback_.call(message->level, message->tag, this->tx_buffer_); | ||||||
|       // At this point all the data we need from message has been transferred to the tx_buffer |       // At this point all the data we need from message has been transferred to the tx_buffer | ||||||
|       // so we can release the message to allow other tasks to use it as soon as possible. |       // so we can release the message to allow other tasks to use it as soon as possible. | ||||||
|       this->log_buffer_->release_message_main_loop(received_token); |       this->log_buffer_->release_message_main_loop(received_token); | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user