mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 23:21:54 +00:00 
			
		
		
		
	Merge branch 'dev' into jesserockz-2025-457
This commit is contained in:
		| @@ -1 +1 @@ | |||||||
| 4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9 | 499db61c1aa55b98b6629df603a56a1ba7aff5a9a7c781a5c1552a9dcd186c08 | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -466,7 +466,7 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           python-version: ${{ env.DEFAULT_PYTHON }} |           python-version: ${{ env.DEFAULT_PYTHON }} | ||||||
|           cache-key: ${{ needs.common.outputs.cache-key }} |           cache-key: ${{ needs.common.outputs.cache-key }} | ||||||
|       - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 |       - uses: esphome/action@43cd1109c09c544d97196f7730ee5b2e0cc6d81e # v3.0.1 fork with pinned actions/cache | ||||||
|         env: |         env: | ||||||
|           SKIP: pylint,clang-tidy-hash |           SKIP: pylint,clang-tidy-hash | ||||||
|       - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 |       - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							| @@ -58,7 +58,7 @@ jobs: | |||||||
|  |  | ||||||
|       # Initializes the CodeQL tools for scanning. |       # Initializes the CodeQL tools for scanning. | ||||||
|       - name: Initialize CodeQL |       - name: Initialize CodeQL | ||||||
|         uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 |         uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 | ||||||
|         with: |         with: | ||||||
|           languages: ${{ matrix.language }} |           languages: ${{ matrix.language }} | ||||||
|           build-mode: ${{ matrix.build-mode }} |           build-mode: ${{ matrix.build-mode }} | ||||||
| @@ -86,6 +86,6 @@ jobs: | |||||||
|           exit 1 |           exit 1 | ||||||
|  |  | ||||||
|       - name: Perform CodeQL Analysis |       - name: Perform CodeQL Analysis | ||||||
|         uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 |         uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 | ||||||
|         with: |         with: | ||||||
|           category: "/language:${{matrix.language}}" |           category: "/language:${{matrix.language}}" | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -19,7 +19,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Stale |       - name: Stale | ||||||
|         uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 |         uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 | ||||||
|         with: |         with: | ||||||
|           debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch |           debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch | ||||||
|           remove-stale-when-updated: true |           remove-stale-when-updated: true | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ ci: | |||||||
| 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.13.2 |     rev: v0.13.3 | ||||||
|     hooks: |     hooks: | ||||||
|       # Run the linter. |       # Run the linter. | ||||||
|       - id: ruff |       - id: ruff | ||||||
|   | |||||||
| @@ -160,7 +160,6 @@ esphome/components/esp_ldo/* @clydebarrow | |||||||
| esphome/components/espnow/* @jesserockz | esphome/components/espnow/* @jesserockz | ||||||
| esphome/components/ethernet_info/* @gtjadsonsantos | esphome/components/ethernet_info/* @gtjadsonsantos | ||||||
| esphome/components/event/* @nohat | esphome/components/event/* @nohat | ||||||
| esphome/components/event_emitter/* @Rapsssito |  | ||||||
| esphome/components/exposure_notifications/* @OttoWinter | esphome/components/exposure_notifications/* @OttoWinter | ||||||
| esphome/components/ezo/* @ssieb | esphome/components/ezo/* @ssieb | ||||||
| esphome/components/ezo_pmp/* @carlos-sarmiento | esphome/components/ezo_pmp/* @carlos-sarmiento | ||||||
| @@ -257,6 +256,7 @@ esphome/components/libretiny_pwm/* @kuba2k2 | |||||||
| esphome/components/light/* @esphome/core | esphome/components/light/* @esphome/core | ||||||
| esphome/components/lightwaverf/* @max246 | esphome/components/lightwaverf/* @max246 | ||||||
| esphome/components/lilygo_t5_47/touchscreen/* @jesserockz | esphome/components/lilygo_t5_47/touchscreen/* @jesserockz | ||||||
|  | esphome/components/lm75b/* @beormund | ||||||
| esphome/components/ln882x/* @lamauny | esphome/components/ln882x/* @lamauny | ||||||
| esphome/components/lock/* @esphome/core | esphome/components/lock/* @esphome/core | ||||||
| esphome/components/logger/* @esphome/core | esphome/components/logger/* @esphome/core | ||||||
|   | |||||||
| @@ -14,9 +14,11 @@ from typing import Protocol | |||||||
|  |  | ||||||
| import argcomplete | import argcomplete | ||||||
|  |  | ||||||
|  | # Note: Do not import modules from esphome.components here, as this would | ||||||
|  | # cause them to be loaded before external components are processed, resulting | ||||||
|  | # in the built-in version being used instead of the external component one. | ||||||
| from esphome import const, writer, yaml_util | from esphome import const, writer, yaml_util | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components.mqtt import CONF_DISCOVER_IP |  | ||||||
| from esphome.config import iter_component_configs, read_config, strip_default_ids | from esphome.config import iter_component_configs, read_config, strip_default_ids | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     ALLOWED_NAME_CHARS, |     ALLOWED_NAME_CHARS, | ||||||
| @@ -240,6 +242,8 @@ def has_ota() -> bool: | |||||||
|  |  | ||||||
| def has_mqtt_ip_lookup() -> bool: | def has_mqtt_ip_lookup() -> bool: | ||||||
|     """Check if MQTT is available and IP lookup is supported.""" |     """Check if MQTT is available and IP lookup is supported.""" | ||||||
|  |     from esphome.components.mqtt import CONF_DISCOVER_IP | ||||||
|  |  | ||||||
|     if CONF_MQTT not in CORE.config: |     if CONF_MQTT not in CORE.config: | ||||||
|         return False |         return False | ||||||
|     # Default Enabled |     # Default Enabled | ||||||
|   | |||||||
| @@ -26,12 +26,12 @@ uint32_t Animation::get_animation_frame_count() const { return this->animation_f | |||||||
| int Animation::get_current_frame() const { return this->current_frame_; } | int Animation::get_current_frame() const { return this->current_frame_; } | ||||||
| void Animation::next_frame() { | void Animation::next_frame() { | ||||||
|   this->current_frame_++; |   this->current_frame_++; | ||||||
|   if (loop_count_ && this->current_frame_ == loop_end_frame_ && |   if (loop_count_ && static_cast<uint32_t>(this->current_frame_) == loop_end_frame_ && | ||||||
|       (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { |       (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { | ||||||
|     this->current_frame_ = loop_start_frame_; |     this->current_frame_ = loop_start_frame_; | ||||||
|     this->loop_current_iteration_++; |     this->loop_current_iteration_++; | ||||||
|   } |   } | ||||||
|   if (this->current_frame_ >= animation_frame_count_) { |   if (static_cast<uint32_t>(this->current_frame_) >= animation_frame_count_) { | ||||||
|     this->loop_current_iteration_ = 1; |     this->loop_current_iteration_ = 1; | ||||||
|     this->current_frame_ = 0; |     this->current_frame_ = 0; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ from esphome.const import ( | |||||||
|     CONF_EVENT, |     CONF_EVENT, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_KEY, |     CONF_KEY, | ||||||
|  |     CONF_MAX_CONNECTIONS, | ||||||
|     CONF_ON_CLIENT_CONNECTED, |     CONF_ON_CLIENT_CONNECTED, | ||||||
|     CONF_ON_CLIENT_DISCONNECTED, |     CONF_ON_CLIENT_DISCONNECTED, | ||||||
|     CONF_ON_ERROR, |     CONF_ON_ERROR, | ||||||
| @@ -68,7 +69,7 @@ CONF_CUSTOM_SERVICES = "custom_services" | |||||||
| CONF_HOMEASSISTANT_SERVICES = "homeassistant_services" | CONF_HOMEASSISTANT_SERVICES = "homeassistant_services" | ||||||
| CONF_HOMEASSISTANT_STATES = "homeassistant_states" | CONF_HOMEASSISTANT_STATES = "homeassistant_states" | ||||||
| CONF_LISTEN_BACKLOG = "listen_backlog" | CONF_LISTEN_BACKLOG = "listen_backlog" | ||||||
| CONF_MAX_CONNECTIONS = "max_connections" | CONF_MAX_SEND_QUEUE = "max_send_queue" | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_encryption_key(value): | def validate_encryption_key(value): | ||||||
| @@ -191,6 +192,19 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 host=8,  # Abundant resources |                 host=8,  # Abundant resources | ||||||
|                 ln882x=8,  # Moderate RAM |                 ln882x=8,  # Moderate RAM | ||||||
|             ): cv.int_range(min=1, max=20), |             ): cv.int_range(min=1, max=20), | ||||||
|  |             # Maximum queued send buffers per connection before dropping connection | ||||||
|  |             # Each buffer uses ~8-12 bytes overhead plus actual message size | ||||||
|  |             # Platform defaults based on available RAM and typical message rates: | ||||||
|  |             cv.SplitDefault( | ||||||
|  |                 CONF_MAX_SEND_QUEUE, | ||||||
|  |                 esp8266=5,  # Limited RAM, need to fail fast | ||||||
|  |                 esp32=8,  # More RAM, can buffer more | ||||||
|  |                 rp2040=5,  # Limited RAM | ||||||
|  |                 bk72xx=8,  # Moderate RAM | ||||||
|  |                 rtl87xx=8,  # Moderate RAM | ||||||
|  |                 host=16,  # Abundant resources | ||||||
|  |                 ln882x=8,  # Moderate RAM | ||||||
|  |             ): cv.int_range(min=1, max=64), | ||||||
|         } |         } | ||||||
|     ).extend(cv.COMPONENT_SCHEMA), |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|     cv.rename_key(CONF_SERVICES, CONF_ACTIONS), |     cv.rename_key(CONF_SERVICES, CONF_ACTIONS), | ||||||
| @@ -213,6 +227,7 @@ async def to_code(config): | |||||||
|         cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG])) |         cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG])) | ||||||
|     if CONF_MAX_CONNECTIONS in config: |     if CONF_MAX_CONNECTIONS in config: | ||||||
|         cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS])) |         cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS])) | ||||||
|  |     cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE]) | ||||||
|  |  | ||||||
|     # Set USE_API_SERVICES if any services are enabled |     # Set USE_API_SERVICES if any services are enabled | ||||||
|     if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: |     if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: | ||||||
|   | |||||||
| @@ -116,8 +116,7 @@ void APIConnection::start() { | |||||||
|  |  | ||||||
|   APIError err = this->helper_->init(); |   APIError err = this->helper_->init(); | ||||||
|   if (err != APIError::OK) { |   if (err != APIError::OK) { | ||||||
|     on_fatal_error(); |     this->fatal_error_with_log_(LOG_STR("Helper init failed"), err); | ||||||
|     this->log_warning_(LOG_STR("Helper init failed"), err); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->client_info_.peername = helper_->getpeername(); |   this->client_info_.peername = helper_->getpeername(); | ||||||
| @@ -147,8 +146,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(); |     this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err); | ||||||
|     this->log_socket_operation_failed_(err); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -163,17 +161,13 @@ void APIConnection::loop() { | |||||||
|         // No more data available |         // No more data available | ||||||
|         break; |         break; | ||||||
|       } else if (err != APIError::OK) { |       } else if (err != APIError::OK) { | ||||||
|         on_fatal_error(); |         this->fatal_error_with_log_(LOG_STR("Reading failed"), err); | ||||||
|         this->log_warning_(LOG_STR("Reading failed"), err); |  | ||||||
|         return; |         return; | ||||||
|       } else { |       } else { | ||||||
|         this->last_traffic_ = now; |         this->last_traffic_ = now; | ||||||
|         // read a packet |         // read a packet | ||||||
|         if (buffer.data_len > 0) { |         this->read_message(buffer.data_len, buffer.type, | ||||||
|           this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]); |                            buffer.data_len > 0 ? &buffer.container[buffer.data_offset] : nullptr); | ||||||
|         } else { |  | ||||||
|           this->read_message(0, buffer.type, nullptr); |  | ||||||
|         } |  | ||||||
|         if (this->flags_.remove) |         if (this->flags_.remove) | ||||||
|           return; |           return; | ||||||
|       } |       } | ||||||
| @@ -205,7 +199,8 @@ 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_DISCONNECT_TIMEOUT) { |     if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) { | ||||||
|       on_fatal_error(); |       on_fatal_error(); | ||||||
|       ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str()); |       ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(), | ||||||
|  |                this->client_info_.peername.c_str()); | ||||||
|     } |     } | ||||||
|   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) { |   } else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) { | ||||||
|     // Only send ping if we're not disconnecting |     // Only send ping if we're not disconnecting | ||||||
| @@ -255,7 +250,7 @@ bool APIConnection::send_disconnect_response(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->get_client_combined_info().c_str()); |   ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); | ||||||
|   this->flags_.next_close = true; |   this->flags_.next_close = true; | ||||||
|   DisconnectResponse resp; |   DisconnectResponse resp; | ||||||
|   return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE); |   return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE); | ||||||
| @@ -1385,7 +1380,7 @@ void APIConnection::complete_authentication_() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); |   this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED); | ||||||
|   ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str()); |   ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); | ||||||
| #ifdef USE_API_CLIENT_CONNECTED_TRIGGER | #ifdef USE_API_CLIENT_CONNECTED_TRIGGER | ||||||
|   this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername); |   this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername); | ||||||
| #endif | #endif | ||||||
| @@ -1394,6 +1389,11 @@ void APIConnection::complete_authentication_() { | |||||||
|     this->send_time_request(); |     this->send_time_request(); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_ZWAVE_PROXY | ||||||
|  |   if (zwave_proxy::global_zwave_proxy != nullptr) { | ||||||
|  |     zwave_proxy::global_zwave_proxy->api_connection_authenticated(this); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| bool APIConnection::send_hello_response(const HelloRequest &msg) { | bool APIConnection::send_hello_response(const HelloRequest &msg) { | ||||||
| @@ -1586,8 +1586,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) { | |||||||
|   delay(0); |   delay(0); | ||||||
|   APIError err = this->helper_->loop(); |   APIError err = this->helper_->loop(); | ||||||
|   if (err != APIError::OK) { |   if (err != APIError::OK) { | ||||||
|     on_fatal_error(); |     this->fatal_error_with_log_(LOG_STR("Socket operation failed"), err); | ||||||
|     this->log_socket_operation_failed_(err); |  | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|   if (this->helper_->can_write_without_blocking()) |   if (this->helper_->can_write_without_blocking()) | ||||||
| @@ -1606,8 +1605,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { | |||||||
|   if (err == APIError::WOULD_BLOCK) |   if (err == APIError::WOULD_BLOCK) | ||||||
|     return false; |     return false; | ||||||
|   if (err != APIError::OK) { |   if (err != APIError::OK) { | ||||||
|     on_fatal_error(); |     this->fatal_error_with_log_(LOG_STR("Packet write failed"), err); | ||||||
|     this->log_warning_(LOG_STR("Packet write failed"), err); |  | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|   // Do not set last_traffic_ on send |   // Do not set last_traffic_ on send | ||||||
| @@ -1616,12 +1614,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { | |||||||
| #ifdef USE_API_PASSWORD | #ifdef USE_API_PASSWORD | ||||||
| void APIConnection::on_unauthenticated_access() { | void APIConnection::on_unauthenticated_access() { | ||||||
|   this->on_fatal_error(); |   this->on_fatal_error(); | ||||||
|   ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str()); |   ESP_LOGD(TAG, "%s (%s) no authentication", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| void APIConnection::on_no_setup_connection() { | void APIConnection::on_no_setup_connection() { | ||||||
|   this->on_fatal_error(); |   this->on_fatal_error(); | ||||||
|   ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str()); |   ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str()); | ||||||
| } | } | ||||||
| void APIConnection::on_fatal_error() { | void APIConnection::on_fatal_error() { | ||||||
|   this->helper_->close(); |   this->helper_->close(); | ||||||
| @@ -1793,8 +1791,7 @@ void APIConnection::process_batch_() { | |||||||
|   APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf}, |   APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&shared_buf}, | ||||||
|                                                        std::span<const PacketInfo>(packet_info, packet_count)); |                                                        std::span<const PacketInfo>(packet_info, packet_count)); | ||||||
|   if (err != APIError::OK && err != APIError::WOULD_BLOCK) { |   if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||||
|     on_fatal_error(); |     this->fatal_error_with_log_(LOG_STR("Batch write failed"), err); | ||||||
|     this->log_warning_(LOG_STR("Batch write failed"), err); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| @@ -1873,12 +1870,8 @@ void APIConnection::process_state_subscriptions_() { | |||||||
| #endif  // USE_API_HOMEASSISTANT_STATES | #endif  // USE_API_HOMEASSISTANT_STATES | ||||||
|  |  | ||||||
| void APIConnection::log_warning_(const LogString *message, APIError err) { | void APIConnection::log_warning_(const LogString *message, APIError err) { | ||||||
|   ESP_LOGW(TAG, "%s: %s %s errno=%d", this->get_client_combined_info().c_str(), LOG_STR_ARG(message), |   ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(), | ||||||
|            LOG_STR_ARG(api_error_to_logstr(err)), errno); |            LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno); | ||||||
| } |  | ||||||
|  |  | ||||||
| void APIConnection::log_socket_operation_failed_(APIError err) { |  | ||||||
|   this->log_warning_(LOG_STR("Socket operation failed"), err); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace esphome::api | }  // namespace esphome::api | ||||||
|   | |||||||
| @@ -19,14 +19,6 @@ namespace esphome::api { | |||||||
| struct ClientInfo { | struct ClientInfo { | ||||||
|   std::string name;      // Client name from Hello message |   std::string name;      // Client name from Hello message | ||||||
|   std::string peername;  // IP:port from socket |   std::string peername;  // IP:port from socket | ||||||
|  |  | ||||||
|   std::string get_combined_info() const { |  | ||||||
|     if (name == peername) { |  | ||||||
|       // Before Hello message, both are the same |  | ||||||
|       return name; |  | ||||||
|     } |  | ||||||
|     return name + " (" + peername + ")"; |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Keepalive timeout in milliseconds | // Keepalive timeout in milliseconds | ||||||
| @@ -281,7 +273,8 @@ class APIConnection final : public APIServerConnection { | |||||||
|   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, uint8_t message_type) override; |   bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; | ||||||
|  |  | ||||||
|   std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); } |   const std::string &get_name() const { return this->client_info_.name; } | ||||||
|  |   const std::string &get_peername() const { return this->client_info_.peername; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   // Helper function to handle authentication completion |   // Helper function to handle authentication completion | ||||||
| @@ -742,8 +735,11 @@ class APIConnection final : public APIServerConnection { | |||||||
|  |  | ||||||
|   // Helper function to log API errors with errno |   // Helper function to log API errors with errno | ||||||
|   void log_warning_(const LogString *message, APIError err); |   void log_warning_(const LogString *message, APIError err); | ||||||
|   // Specific helper for duplicated error message |   // Helper to handle fatal errors with logging | ||||||
|   void log_socket_operation_failed_(APIError err); |   inline void fatal_error_with_log_(const LogString *message, APIError err) { | ||||||
|  |     this->on_fatal_error(); | ||||||
|  |     this->log_warning_(message, err); | ||||||
|  |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace esphome::api | }  // namespace esphome::api | ||||||
|   | |||||||
| @@ -13,7 +13,8 @@ namespace esphome::api { | |||||||
|  |  | ||||||
| static const char *const TAG = "api.frame_helper"; | static const char *const TAG = "api.frame_helper"; | ||||||
|  |  | ||||||
| #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) | #define HELPER_LOG(msg, ...) \ | ||||||
|  |   ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) | ||||||
|  |  | ||||||
| #ifdef HELPER_LOG_PACKETS | #ifdef HELPER_LOG_PACKETS | ||||||
| #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) | #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) | ||||||
| @@ -80,7 +81,7 @@ const LogString *api_error_to_logstr(APIError err) { | |||||||
|  |  | ||||||
| // Default implementation for loop - handles sending buffered data | // Default implementation for loop - handles sending buffered data | ||||||
| APIError APIFrameHelper::loop() { | APIError APIFrameHelper::loop() { | ||||||
|   if (!this->tx_buf_.empty()) { |   if (this->tx_buf_count_ > 0) { | ||||||
|     APIError err = try_send_tx_buf_(); |     APIError err = try_send_tx_buf_(); | ||||||
|     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { |     if (err != APIError::OK && err != APIError::WOULD_BLOCK) { | ||||||
|       return err; |       return err; | ||||||
| @@ -102,9 +103,20 @@ APIError APIFrameHelper::handle_socket_write_error_() { | |||||||
| // Helper method to buffer data from IOVs | // Helper method to buffer data from IOVs | ||||||
| void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, | void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, | ||||||
|                                            uint16_t offset) { |                                            uint16_t offset) { | ||||||
|   SendBuffer buffer; |   // Check if queue is full | ||||||
|   buffer.size = total_write_len - offset; |   if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) { | ||||||
|   buffer.data = std::make_unique<uint8_t[]>(buffer.size); |     HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_); | ||||||
|  |     this->state_ = State::FAILED; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint16_t buffer_size = total_write_len - offset; | ||||||
|  |   auto &buffer = this->tx_buf_[this->tx_buf_tail_]; | ||||||
|  |   buffer = std::make_unique<SendBuffer>(SendBuffer{ | ||||||
|  |       .data = std::make_unique<uint8_t[]>(buffer_size), | ||||||
|  |       .size = buffer_size, | ||||||
|  |       .offset = 0, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   uint16_t to_skip = offset; |   uint16_t to_skip = offset; | ||||||
|   uint16_t write_pos = 0; |   uint16_t write_pos = 0; | ||||||
| @@ -117,12 +129,15 @@ void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, | |||||||
|       // Include this segment (partially or fully) |       // Include this segment (partially or fully) | ||||||
|       const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip; |       const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip; | ||||||
|       uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip; |       uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip; | ||||||
|       std::memcpy(buffer.data.get() + write_pos, src, len); |       std::memcpy(buffer->data.get() + write_pos, src, len); | ||||||
|       write_pos += len; |       write_pos += len; | ||||||
|       to_skip = 0; |       to_skip = 0; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   this->tx_buf_.push_back(std::move(buffer)); |  | ||||||
|  |   // Update circular buffer tracking | ||||||
|  |   this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE; | ||||||
|  |   this->tx_buf_count_++; | ||||||
| } | } | ||||||
|  |  | ||||||
| // This method writes data to socket or buffers it | // This method writes data to socket or buffers it | ||||||
| @@ -140,7 +155,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_ | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   // Try to send any existing buffered data first if there is any |   // Try to send any existing buffered data first if there is any | ||||||
|   if (!this->tx_buf_.empty()) { |   if (this->tx_buf_count_ > 0) { | ||||||
|     APIError send_result = try_send_tx_buf_(); |     APIError send_result = try_send_tx_buf_(); | ||||||
|     // If real error occurred (not just WOULD_BLOCK), return it |     // If real error occurred (not just WOULD_BLOCK), return it | ||||||
|     if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) { |     if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) { | ||||||
| @@ -149,7 +164,7 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_ | |||||||
|  |  | ||||||
|     // If there is still data in the buffer, we can't send, buffer |     // If there is still data in the buffer, we can't send, buffer | ||||||
|     // the new data and return |     // the new data and return | ||||||
|     if (!this->tx_buf_.empty()) { |     if (this->tx_buf_count_ > 0) { | ||||||
|       this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); |       this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); | ||||||
|       return APIError::OK;  // Success, data buffered |       return APIError::OK;  // Success, data buffered | ||||||
|     } |     } | ||||||
| @@ -177,32 +192,31 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_ | |||||||
| } | } | ||||||
|  |  | ||||||
| // Common implementation for trying to send buffered data | // Common implementation for trying to send buffered data | ||||||
| // IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method | // IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method | ||||||
| APIError APIFrameHelper::try_send_tx_buf_() { | APIError APIFrameHelper::try_send_tx_buf_() { | ||||||
|   // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check |   // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check | ||||||
|   bool tx_buf_empty = false; |   while (this->tx_buf_count_ > 0) { | ||||||
|   while (!tx_buf_empty) { |  | ||||||
|     // Get the first buffer in the queue |     // Get the first buffer in the queue | ||||||
|     SendBuffer &front_buffer = this->tx_buf_.front(); |     SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get(); | ||||||
|  |  | ||||||
|     // Try to send the remaining data in this buffer |     // Try to send the remaining data in this buffer | ||||||
|     ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining()); |     ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining()); | ||||||
|  |  | ||||||
|     if (sent == -1) { |     if (sent == -1) { | ||||||
|       return this->handle_socket_write_error_(); |       return this->handle_socket_write_error_(); | ||||||
|     } else if (sent == 0) { |     } else if (sent == 0) { | ||||||
|       // Nothing sent but not an error |       // Nothing sent but not an error | ||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) { |     } else if (static_cast<uint16_t>(sent) < front_buffer->remaining()) { | ||||||
|       // Partially sent, update offset |       // Partially sent, update offset | ||||||
|       // Cast to ensure no overflow issues with uint16_t |       // Cast to ensure no overflow issues with uint16_t | ||||||
|       front_buffer.offset += static_cast<uint16_t>(sent); |       front_buffer->offset += static_cast<uint16_t>(sent); | ||||||
|       return APIError::WOULD_BLOCK;  // Stop processing more buffers if we couldn't send a complete buffer |       return APIError::WOULD_BLOCK;  // Stop processing more buffers if we couldn't send a complete buffer | ||||||
|     } else { |     } else { | ||||||
|       // Buffer completely sent, remove it from the queue |       // Buffer completely sent, remove it from the queue | ||||||
|       this->tx_buf_.pop_front(); |       this->tx_buf_[this->tx_buf_head_].reset(); | ||||||
|       // Update empty status for the loop condition |       this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE; | ||||||
|       tx_buf_empty = this->tx_buf_.empty(); |       this->tx_buf_count_--; | ||||||
|       // Continue loop to try sending the next buffer |       // Continue loop to try sending the next buffer | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  | #include <array> | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
| #include <deque> |  | ||||||
| #include <limits> | #include <limits> | ||||||
|  | #include <memory> | ||||||
| #include <span> | #include <span> | ||||||
| #include <utility> | #include <utility> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -79,7 +80,7 @@ class APIFrameHelper { | |||||||
|   virtual APIError init() = 0; |   virtual APIError init() = 0; | ||||||
|   virtual APIError loop(); |   virtual APIError loop(); | ||||||
|   virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; |   virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; | ||||||
|   bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } |   bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; } | ||||||
|   std::string getpeername() { return socket_->getpeername(); } |   std::string getpeername() { return socket_->getpeername(); } | ||||||
|   int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } |   int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } | ||||||
|   APIError close() { |   APIError close() { | ||||||
| @@ -161,7 +162,7 @@ class APIFrameHelper { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // Containers (size varies, but typically 12+ bytes on 32-bit) |   // Containers (size varies, but typically 12+ bytes on 32-bit) | ||||||
|   std::deque<SendBuffer> tx_buf_; |   std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_; | ||||||
|   std::vector<struct iovec> reusable_iovs_; |   std::vector<struct iovec> reusable_iovs_; | ||||||
|   std::vector<uint8_t> rx_buf_; |   std::vector<uint8_t> rx_buf_; | ||||||
|  |  | ||||||
| @@ -174,7 +175,10 @@ class APIFrameHelper { | |||||||
|   State state_{State::INITIALIZE}; |   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 |   uint8_t tx_buf_head_{0}; | ||||||
|  |   uint8_t tx_buf_tail_{0}; | ||||||
|  |   uint8_t tx_buf_count_{0}; | ||||||
|  |   // 8 bytes total, 0 bytes padding | ||||||
|  |  | ||||||
|   // Common initialization for both plaintext and noise protocols |   // Common initialization for both plaintext and noise protocols | ||||||
|   APIError init_common_(); |   APIError init_common_(); | ||||||
|   | |||||||
| @@ -24,7 +24,8 @@ static const char *const PROLOGUE_INIT = "NoiseAPIInit"; | |||||||
| #endif | #endif | ||||||
| static constexpr size_t PROLOGUE_INIT_LEN = 12;  // strlen("NoiseAPIInit") | static constexpr size_t PROLOGUE_INIT_LEN = 12;  // strlen("NoiseAPIInit") | ||||||
|  |  | ||||||
| #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) | #define HELPER_LOG(msg, ...) \ | ||||||
|  |   ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) | ||||||
|  |  | ||||||
| #ifdef HELPER_LOG_PACKETS | #ifdef HELPER_LOG_PACKETS | ||||||
| #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) | #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) | ||||||
|   | |||||||
| @@ -18,7 +18,8 @@ namespace esphome::api { | |||||||
|  |  | ||||||
| static const char *const TAG = "api.plaintext"; | static const char *const TAG = "api.plaintext"; | ||||||
|  |  | ||||||
| #define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__) | #define HELPER_LOG(msg, ...) \ | ||||||
|  |   ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__) | ||||||
|  |  | ||||||
| #ifdef HELPER_LOG_PACKETS | #ifdef HELPER_LOG_PACKETS | ||||||
| #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) | #define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str()) | ||||||
|   | |||||||
| @@ -181,7 +181,8 @@ void APIServer::loop() { | |||||||
|     // Network is down - disconnect all clients |     // Network is down - disconnect all clients | ||||||
|     for (auto &client : this->clients_) { |     for (auto &client : this->clients_) { | ||||||
|       client->on_fatal_error(); |       client->on_fatal_error(); | ||||||
|       ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str()); |       ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(), | ||||||
|  |                client->client_info_.peername.c_str()); | ||||||
|     } |     } | ||||||
|     // Continue to process and clean up the clients below |     // Continue to process and clean up the clients below | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   virtual void execute(Ts... x) = 0; |   virtual void execute(Ts... x) = 0; | ||||||
|   template<int... S> void execute_(std::vector<ExecuteServiceArgument> args, seq<S...> type) { |   template<int... S> void execute_(const std::vector<ExecuteServiceArgument> &args, seq<S...> type) { | ||||||
|     this->execute((get_execute_arg_value<Ts>(args[S]))...); |     this->execute((get_execute_arg_value<Ts>(args[S]))...); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ const char *audio_file_type_to_string(AudioFileType file_type) { | |||||||
| void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor, | void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor, | ||||||
|                          size_t samples_to_scale) { |                          size_t samples_to_scale) { | ||||||
|   // Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same. |   // Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same. | ||||||
|   for (int i = 0; i < samples_to_scale; i++) { |   for (size_t i = 0; i < samples_to_scale; i++) { | ||||||
|     int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor; |     int32_t acc = (int32_t) audio_samples[i] * (int32_t) scale_factor; | ||||||
|     output_buffer[i] = (int16_t) (acc >> 15); |     output_buffer[i] = (int16_t) (acc >> 15); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -97,10 +97,10 @@ void BL0906::handle_actions_() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   ActionCallbackFuncPtr ptr_func = nullptr; |   ActionCallbackFuncPtr ptr_func = nullptr; | ||||||
|   for (int i = 0; i < this->action_queue_.size(); i++) { |   for (size_t i = 0; i < this->action_queue_.size(); i++) { | ||||||
|     ptr_func = this->action_queue_[i]; |     ptr_func = this->action_queue_[i]; | ||||||
|     if (ptr_func) { |     if (ptr_func) { | ||||||
|       ESP_LOGI(TAG, "HandleActionCallback[%d]", i); |       ESP_LOGI(TAG, "HandleActionCallback[%zu]", i); | ||||||
|       (this->*ptr_func)(); |       (this->*ptr_func)(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -51,7 +51,7 @@ void BL0942::loop() { | |||||||
|   if (!avail) { |   if (!avail) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (avail < sizeof(buffer)) { |   if (static_cast<size_t>(avail) < sizeof(buffer)) { | ||||||
|     if (!this->rx_start_) { |     if (!this->rx_start_) { | ||||||
|       this->rx_start_ = millis(); |       this->rx_start_ = millis(); | ||||||
|     } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) { |     } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) { | ||||||
| @@ -148,7 +148,7 @@ void BL0942::setup() { | |||||||
|  |  | ||||||
|   this->write_reg_(BL0942_REG_USR_WRPROT, 0); |   this->write_reg_(BL0942_REG_USR_WRPROT, 0); | ||||||
|  |  | ||||||
|   if (this->read_reg_(BL0942_REG_MODE) != mode) |   if (static_cast<uint32_t>(this->read_reg_(BL0942_REG_MODE)) != mode) | ||||||
|     this->status_set_warning(LOG_STR("BL0942 setup failed!")); |     this->status_set_warning(LOG_STR("BL0942 setup failed!")); | ||||||
|  |  | ||||||
|   this->flush(); |   this->flush(); | ||||||
|   | |||||||
| @@ -116,7 +116,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|     ) |     ) | ||||||
|     .extend(cv.COMPONENT_SCHEMA) |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA), |     .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA), | ||||||
|     esp32_ble_tracker.consume_connection_slots(1, "ble_client"), |     esp32_ble.consume_connection_slots(1, "ble_client"), | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONF_BLE_CLIENT_ID = "ble_client_id" | CONF_BLE_CLIENT_ID = "ble_client_id" | ||||||
|   | |||||||
| @@ -42,9 +42,7 @@ def validate_connections(config): | |||||||
|             ) |             ) | ||||||
|     elif config[CONF_ACTIVE]: |     elif config[CONF_ACTIVE]: | ||||||
|         connection_slots: int = config[CONF_CONNECTION_SLOTS] |         connection_slots: int = config[CONF_CONNECTION_SLOTS] | ||||||
|         esp32_ble_tracker.consume_connection_slots(connection_slots, "bluetooth_proxy")( |         esp32_ble.consume_connection_slots(connection_slots, "bluetooth_proxy")(config) | ||||||
|             config |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         return { |         return { | ||||||
|             **config, |             **config, | ||||||
| @@ -65,11 +63,11 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                     default=DEFAULT_CONNECTION_SLOTS, |                     default=DEFAULT_CONNECTION_SLOTS, | ||||||
|                 ): cv.All( |                 ): cv.All( | ||||||
|                     cv.positive_int, |                     cv.positive_int, | ||||||
|                     cv.Range(min=1, max=esp32_ble_tracker.IDF_MAX_CONNECTIONS), |                     cv.Range(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS), | ||||||
|                 ), |                 ), | ||||||
|                 cv.Optional(CONF_CONNECTIONS): cv.All( |                 cv.Optional(CONF_CONNECTIONS): cv.All( | ||||||
|                     cv.ensure_list(CONNECTION_SCHEMA), |                     cv.ensure_list(CONNECTION_SCHEMA), | ||||||
|                     cv.Length(min=1, max=esp32_ble_tracker.IDF_MAX_CONNECTIONS), |                     cv.Length(min=1, max=esp32_ble.IDF_MAX_CONNECTIONS), | ||||||
|                 ), |                 ), | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|   | |||||||
| @@ -11,14 +11,14 @@ namespace captive_portal { | |||||||
| static const char *const TAG = "captive_portal"; | static const char *const TAG = "captive_portal"; | ||||||
|  |  | ||||||
| void CaptivePortal::handle_config(AsyncWebServerRequest *request) { | void CaptivePortal::handle_config(AsyncWebServerRequest *request) { | ||||||
|   AsyncResponseStream *stream = request->beginResponseStream(F("application/json")); |   AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json")); | ||||||
|   stream->addHeader(F("cache-control"), F("public, max-age=0, must-revalidate")); |   stream->addHeader(ESPHOME_F("cache-control"), ESPHOME_F("public, max-age=0, must-revalidate")); | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
|   stream->print(F("{\"mac\":\"")); |   stream->print(ESPHOME_F("{\"mac\":\"")); | ||||||
|   stream->print(get_mac_address_pretty().c_str()); |   stream->print(get_mac_address_pretty().c_str()); | ||||||
|   stream->print(F("\",\"name\":\"")); |   stream->print(ESPHOME_F("\",\"name\":\"")); | ||||||
|   stream->print(App.get_name().c_str()); |   stream->print(App.get_name().c_str()); | ||||||
|   stream->print(F("\",\"aps\":[{}")); |   stream->print(ESPHOME_F("\",\"aps\":[{}")); | ||||||
| #else | #else | ||||||
|   stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str()); |   stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str()); | ||||||
| #endif | #endif | ||||||
| @@ -29,19 +29,19 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) { | |||||||
|  |  | ||||||
|       // Assumes no " in ssid, possible unicode isses? |       // Assumes no " in ssid, possible unicode isses? | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
|     stream->print(F(",{\"ssid\":\"")); |     stream->print(ESPHOME_F(",{\"ssid\":\"")); | ||||||
|     stream->print(scan.get_ssid().c_str()); |     stream->print(scan.get_ssid().c_str()); | ||||||
|     stream->print(F("\",\"rssi\":")); |     stream->print(ESPHOME_F("\",\"rssi\":")); | ||||||
|     stream->print(scan.get_rssi()); |     stream->print(scan.get_rssi()); | ||||||
|     stream->print(F(",\"lock\":")); |     stream->print(ESPHOME_F(",\"lock\":")); | ||||||
|     stream->print(scan.get_with_auth()); |     stream->print(scan.get_with_auth()); | ||||||
|     stream->print(F("}")); |     stream->print(ESPHOME_F("}")); | ||||||
| #else | #else | ||||||
|     stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(), |     stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(), | ||||||
|                    scan.get_with_auth()); |                    scan.get_with_auth()); | ||||||
| #endif | #endif | ||||||
|   } |   } | ||||||
|   stream->print(F("]}")); |   stream->print(ESPHOME_F("]}")); | ||||||
|   request->send(stream); |   request->send(stream); | ||||||
| } | } | ||||||
| void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { | void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { | ||||||
| @@ -52,7 +52,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { | |||||||
|   ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str()); |   ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str()); | ||||||
|   wifi::global_wifi_component->save_wifi_sta(ssid, psk); |   wifi::global_wifi_component->save_wifi_sta(ssid, psk); | ||||||
|   wifi::global_wifi_component->start_scanning(); |   wifi::global_wifi_component->start_scanning(); | ||||||
|   request->redirect(F("/?save")); |   request->redirect(ESPHOME_F("/?save")); | ||||||
| } | } | ||||||
|  |  | ||||||
| void CaptivePortal::setup() { | void CaptivePortal::setup() { | ||||||
| @@ -75,7 +75,7 @@ void CaptivePortal::start() { | |||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
|   this->dns_server_ = make_unique<DNSServer>(); |   this->dns_server_ = make_unique<DNSServer>(); | ||||||
|   this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); |   this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); | ||||||
|   this->dns_server_->start(53, F("*"), ip); |   this->dns_server_->start(53, ESPHOME_F("*"), ip); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   this->initialized_ = true; |   this->initialized_ = true; | ||||||
| @@ -88,10 +88,10 @@ void CaptivePortal::start() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { | void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { | ||||||
|   if (req->url() == F("/config.json")) { |   if (req->url() == ESPHOME_F("/config.json")) { | ||||||
|     this->handle_config(req); |     this->handle_config(req); | ||||||
|     return; |     return; | ||||||
|   } else if (req->url() == F("/wifisave")) { |   } else if (req->url() == ESPHOME_F("/wifisave")) { | ||||||
|     this->handle_wifisave(req); |     this->handle_wifisave(req); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -100,11 +100,11 @@ void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { | |||||||
|   // This includes OS captive portal detection endpoints which will trigger |   // This includes OS captive portal detection endpoints which will trigger | ||||||
|   // the captive portal when they don't receive their expected responses |   // the captive portal when they don't receive their expected responses | ||||||
| #ifndef USE_ESP8266 | #ifndef USE_ESP8266 | ||||||
|   auto *response = req->beginResponse(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); |   auto *response = req->beginResponse(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); | ||||||
| #else | #else | ||||||
|   auto *response = req->beginResponse_P(200, F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); |   auto *response = req->beginResponse_P(200, ESPHOME_F("text/html"), INDEX_GZ, sizeof(INDEX_GZ)); | ||||||
| #endif | #endif | ||||||
|   response->addHeader(F("Content-Encoding"), F("gzip")); |   response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip")); | ||||||
|   req->send(response); |   req->send(response); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ static const uint8_t C_M1106_CMD_SET_CO2_CALIB_RESPONSE[4] = {0x16, 0x01, 0x03, | |||||||
|  |  | ||||||
| uint8_t cm1106_checksum(const uint8_t *response, size_t len) { | uint8_t cm1106_checksum(const uint8_t *response, size_t len) { | ||||||
|   uint8_t crc = 0; |   uint8_t crc = 0; | ||||||
|   for (int i = 0; i < len - 1; i++) { |   for (size_t i = 0; i < len - 1; i++) { | ||||||
|     crc -= response[i]; |     crc -= response[i]; | ||||||
|   } |   } | ||||||
|   return crc; |   return crc; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ void CopyLock::setup() { | |||||||
|  |  | ||||||
|   traits.set_assumed_state(source_->traits.get_assumed_state()); |   traits.set_assumed_state(source_->traits.get_assumed_state()); | ||||||
|   traits.set_requires_code(source_->traits.get_requires_code()); |   traits.set_requires_code(source_->traits.get_requires_code()); | ||||||
|   traits.set_supported_states(source_->traits.get_supported_states()); |   traits.set_supported_states_mask(source_->traits.get_supported_states_mask()); | ||||||
|   traits.set_supports_open(source_->traits.get_supports_open()); |   traits.set_supports_open(source_->traits.get_supports_open()); | ||||||
|  |  | ||||||
|   this->publish_state(source_->state); |   this->publish_state(source_->state); | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ void DaikinArcClimate::transmit_query_() { | |||||||
|   uint8_t remote_header[8] = {0x11, 0xDA, 0x27, 0x00, 0x84, 0x87, 0x20, 0x00}; |   uint8_t remote_header[8] = {0x11, 0xDA, 0x27, 0x00, 0x84, 0x87, 0x20, 0x00}; | ||||||
|  |  | ||||||
|   // Calculate checksum |   // Calculate checksum | ||||||
|   for (int i = 0; i < sizeof(remote_header) - 1; i++) { |   for (size_t i = 0; i < sizeof(remote_header) - 1; i++) { | ||||||
|     remote_header[sizeof(remote_header) - 1] += remote_header[i]; |     remote_header[sizeof(remote_header) - 1] += remote_header[i]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -102,7 +102,7 @@ void DaikinArcClimate::transmit_state() { | |||||||
|   remote_state[9] = fan_speed & 0xff; |   remote_state[9] = fan_speed & 0xff; | ||||||
|  |  | ||||||
|   // Calculate checksum |   // Calculate checksum | ||||||
|   for (int i = 0; i < sizeof(remote_header) - 1; i++) { |   for (size_t i = 0; i < sizeof(remote_header) - 1; i++) { | ||||||
|     remote_header[sizeof(remote_header) - 1] += remote_header[i]; |     remote_header[sizeof(remote_header) - 1] += remote_header[i]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -350,7 +350,7 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|   bool valid_daikin_frame = false; |   bool valid_daikin_frame = false; | ||||||
|   if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) { |   if (data.expect_item(DAIKIN_HEADER_MARK, DAIKIN_HEADER_SPACE)) { | ||||||
|     valid_daikin_frame = true; |     valid_daikin_frame = true; | ||||||
|     int bytes_count = data.size() / 2 / 8; |     size_t bytes_count = data.size() / 2 / 8; | ||||||
|     std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]); |     std::unique_ptr<char[]> buf(new char[bytes_count * 3 + 1]); | ||||||
|     buf[0] = '\0'; |     buf[0] = '\0'; | ||||||
|     for (size_t i = 0; i < bytes_count; i++) { |     for (size_t i = 0; i < bytes_count; i++) { | ||||||
| @@ -370,7 +370,7 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|   if (!valid_daikin_frame) { |   if (!valid_daikin_frame) { | ||||||
|     char sbuf[16 * 10 + 1]; |     char sbuf[16 * 10 + 1]; | ||||||
|     sbuf[0] = '\0'; |     sbuf[0] = '\0'; | ||||||
|     for (size_t j = 0; j < data.size(); j++) { |     for (size_t j = 0; j < static_cast<size_t>(data.size()); j++) { | ||||||
|       if ((j - 2) % 16 == 0) { |       if ((j - 2) % 16 == 0) { | ||||||
|         if (j > 0) { |         if (j > 0) { | ||||||
|           ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf); |           ESP_LOGD(TAG, "DATA %04x: %s", (j - 16 > 0xffff ? 0 : j - 16), sbuf); | ||||||
| @@ -380,19 +380,26 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|       char type_ch = ' '; |       char type_ch = ' '; | ||||||
|       // debug_tolerance = 25% |       // debug_tolerance = 25% | ||||||
|  |  | ||||||
|       if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_MARK)) |       if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_MARK)) <= data[j] && | ||||||
|  |           data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_MARK))) | ||||||
|         type_ch = 'P'; |         type_ch = 'P'; | ||||||
|       if (DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_SPACE)) |       if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ARC_PRE_SPACE)) <= -data[j] && | ||||||
|  |           -data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ARC_PRE_SPACE))) | ||||||
|         type_ch = 'a'; |         type_ch = 'a'; | ||||||
|       if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_MARK)) |       if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_HEADER_MARK)) <= data[j] && | ||||||
|  |           data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_HEADER_MARK))) | ||||||
|         type_ch = 'H'; |         type_ch = 'H'; | ||||||
|       if (DAIKIN_DBG_LOWER(DAIKIN_HEADER_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_HEADER_SPACE)) |       if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_HEADER_SPACE)) <= -data[j] && | ||||||
|  |           -data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_HEADER_SPACE))) | ||||||
|         type_ch = 'h'; |         type_ch = 'h'; | ||||||
|       if (DAIKIN_DBG_LOWER(DAIKIN_BIT_MARK) <= data[j] && data[j] <= DAIKIN_DBG_UPPER(DAIKIN_BIT_MARK)) |       if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_BIT_MARK)) <= data[j] && | ||||||
|  |           data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_BIT_MARK))) | ||||||
|         type_ch = 'B'; |         type_ch = 'B'; | ||||||
|       if (DAIKIN_DBG_LOWER(DAIKIN_ONE_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ONE_SPACE)) |       if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ONE_SPACE)) <= -data[j] && | ||||||
|  |           -data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ONE_SPACE))) | ||||||
|         type_ch = '1'; |         type_ch = '1'; | ||||||
|       if (DAIKIN_DBG_LOWER(DAIKIN_ZERO_SPACE) <= -data[j] && -data[j] <= DAIKIN_DBG_UPPER(DAIKIN_ZERO_SPACE)) |       if (static_cast<int32_t>(DAIKIN_DBG_LOWER(DAIKIN_ZERO_SPACE)) <= -data[j] && | ||||||
|  |           -data[j] <= static_cast<int32_t>(DAIKIN_DBG_UPPER(DAIKIN_ZERO_SPACE))) | ||||||
|         type_ch = '0'; |         type_ch = '0'; | ||||||
|  |  | ||||||
|       if (abs(data[j]) > 100000) { |       if (abs(data[j]) > 100000) { | ||||||
| @@ -400,7 +407,7 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|       } else { |       } else { | ||||||
|         sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch); |         sprintf(sbuf, "%s%-5d[%c] ", sbuf, (int) (round(data[j] / 10.) * 10), type_ch); | ||||||
|       } |       } | ||||||
|       if (j == data.size() - 1) { |       if (j + 1 == static_cast<size_t>(data.size())) { | ||||||
|         ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf); |         ESP_LOGD(TAG, "DATA %04x: %s", (j - 8 > 0xffff ? 0 : j - 8), sbuf); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -97,12 +97,12 @@ bool ES7210::set_mic_gain(float mic_gain) { | |||||||
| } | } | ||||||
|  |  | ||||||
| bool ES7210::configure_sample_rate_() { | bool ES7210::configure_sample_rate_() { | ||||||
|   int mclk_fre = this->sample_rate_ * MCLK_DIV_FRE; |   uint32_t mclk_fre = this->sample_rate_ * MCLK_DIV_FRE; | ||||||
|   int coeff = -1; |   int coeff = -1; | ||||||
|  |  | ||||||
|   for (int i = 0; i < (sizeof(ES7210_COEFFICIENTS) / sizeof(ES7210_COEFFICIENTS[0])); ++i) { |   for (size_t i = 0; i < (sizeof(ES7210_COEFFICIENTS) / sizeof(ES7210_COEFFICIENTS[0])); ++i) { | ||||||
|     if (ES7210_COEFFICIENTS[i].lrclk == this->sample_rate_ && ES7210_COEFFICIENTS[i].mclk == mclk_fre) |     if (ES7210_COEFFICIENTS[i].lrclk == this->sample_rate_ && ES7210_COEFFICIENTS[i].mclk == mclk_fre) | ||||||
|       coeff = i; |       coeff = static_cast<int>(i); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (coeff >= 0) { |   if (coeff >= 0) { | ||||||
|   | |||||||
| @@ -296,14 +296,9 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: | |||||||
|     return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{str(ver)}/esp32-{str(ver)}.zip" |     return f"pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/{str(ver)}/esp32-{str(ver)}.zip" | ||||||
|  |  | ||||||
|  |  | ||||||
| def _format_framework_espidf_version( | def _format_framework_espidf_version(ver: cv.Version, release: str) -> str: | ||||||
|     ver: cv.Version, release: str, for_platformio: bool |     # format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to | ||||||
| ) -> str: |  | ||||||
|     # format the given arduino (https://github.com/espressif/esp-idf/releases) version to |  | ||||||
|     # a PIO platformio/framework-espidf value |     # a PIO platformio/framework-espidf value | ||||||
|     # List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf |  | ||||||
|     if for_platformio: |  | ||||||
|         return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" |  | ||||||
|     if release: |     if release: | ||||||
|         return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip" |         return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip" | ||||||
|     return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip" |     return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip" | ||||||
| @@ -317,157 +312,108 @@ 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 | ||||||
| RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 2, 1) | ARDUINO_FRAMEWORK_VERSION_LOOKUP = { | ||||||
| # The platform-espressif32 version to use for arduino frameworks |     "recommended": cv.Version(3, 2, 1), | ||||||
| #  - https://github.com/pioarduino/platform-espressif32/releases |     "latest": cv.Version(3, 3, 1), | ||||||
| ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21, "2") |     "dev": cv.Version(3, 3, 1), | ||||||
|  | } | ||||||
|  | ARDUINO_PLATFORM_VERSION_LOOKUP = { | ||||||
|  |     cv.Version(3, 3, 1): cv.Version(55, 3, 31), | ||||||
|  |     cv.Version(3, 3, 0): cv.Version(55, 3, 30, "2"), | ||||||
|  |     cv.Version(3, 2, 1): cv.Version(54, 3, 21, "2"), | ||||||
|  |     cv.Version(3, 2, 0): cv.Version(54, 3, 20), | ||||||
|  |     cv.Version(3, 1, 3): cv.Version(53, 3, 13), | ||||||
|  |     cv.Version(3, 1, 2): cv.Version(53, 3, 12), | ||||||
|  |     cv.Version(3, 1, 1): cv.Version(53, 3, 11), | ||||||
|  |     cv.Version(3, 1, 0): cv.Version(53, 3, 10), | ||||||
|  | } | ||||||
|  |  | ||||||
| # 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 | ||||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf | ESP_IDF_FRAMEWORK_VERSION_LOOKUP = { | ||||||
| RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 4, 2) |     "recommended": cv.Version(5, 4, 2), | ||||||
| # The platformio/espressif32 version to use for esp-idf frameworks |     "latest": cv.Version(5, 5, 1), | ||||||
| #  - https://github.com/platformio/platform-espressif32/releases |     "dev": cv.Version(5, 5, 1), | ||||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 | } | ||||||
| ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21, "2") | ESP_IDF_PLATFORM_VERSION_LOOKUP = { | ||||||
|  |     cv.Version(5, 5, 1): cv.Version(55, 3, 31), | ||||||
|  |     cv.Version(5, 5, 0): cv.Version(55, 3, 31), | ||||||
|  |     cv.Version(5, 4, 2): cv.Version(54, 3, 21, "2"), | ||||||
|  |     cv.Version(5, 4, 1): cv.Version(54, 3, 21, "2"), | ||||||
|  |     cv.Version(5, 4, 0): cv.Version(54, 3, 21, "2"), | ||||||
|  |     cv.Version(5, 3, 2): cv.Version(53, 3, 13), | ||||||
|  |     cv.Version(5, 3, 1): cv.Version(53, 3, 13), | ||||||
|  |     cv.Version(5, 3, 0): cv.Version(53, 3, 13), | ||||||
|  |     cv.Version(5, 1, 6): cv.Version(51, 3, 7), | ||||||
|  |     cv.Version(5, 1, 5): cv.Version(51, 3, 7), | ||||||
|  | } | ||||||
|  |  | ||||||
| # List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions | # The platform-espressif32 version | ||||||
| SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ | #  - https://github.com/pioarduino/platform-espressif32/releases | ||||||
|     cv.Version(5, 3, 1), | PLATFORM_VERSION_LOOKUP = { | ||||||
|     cv.Version(5, 3, 0), |     "recommended": cv.Version(54, 3, 21, "2"), | ||||||
|     cv.Version(5, 2, 2), |     "latest": cv.Version(55, 3, 31), | ||||||
|     cv.Version(5, 2, 1), |     "dev": "https://github.com/pioarduino/platform-espressif32.git#develop", | ||||||
|     cv.Version(5, 1, 2), | } | ||||||
|     cv.Version(5, 1, 1), |  | ||||||
|     cv.Version(5, 1, 0), |  | ||||||
|     cv.Version(5, 0, 2), |  | ||||||
|     cv.Version(5, 0, 1), |  | ||||||
|     cv.Version(5, 0, 0), |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| # pioarduino versions that don't require a release number |  | ||||||
| # List based on https://github.com/pioarduino/esp-idf/releases |  | ||||||
| SUPPORTED_PIOARDUINO_ESP_IDF_5X = [ |  | ||||||
|     cv.Version(5, 5, 1), |  | ||||||
|     cv.Version(5, 5, 0), |  | ||||||
|     cv.Version(5, 4, 2), |  | ||||||
|     cv.Version(5, 4, 1), |  | ||||||
|     cv.Version(5, 4, 0), |  | ||||||
|     cv.Version(5, 3, 3), |  | ||||||
|     cv.Version(5, 3, 2), |  | ||||||
|     cv.Version(5, 3, 1), |  | ||||||
|     cv.Version(5, 3, 0), |  | ||||||
|     cv.Version(5, 1, 5), |  | ||||||
|     cv.Version(5, 1, 6), |  | ||||||
| ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _check_versions(value): | def _check_versions(value): | ||||||
|     value = value.copy() |     value = value.copy() | ||||||
|     if value[CONF_TYPE] == FRAMEWORK_ARDUINO: |  | ||||||
|         lookups = { |  | ||||||
|             "dev": ( |  | ||||||
|                 cv.Version(3, 2, 1), |  | ||||||
|                 "https://github.com/espressif/arduino-esp32.git", |  | ||||||
|             ), |  | ||||||
|             "latest": (cv.Version(3, 2, 1), None), |  | ||||||
|             "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if value[CONF_VERSION] in lookups: |     if value[CONF_VERSION] in PLATFORM_VERSION_LOOKUP: | ||||||
|             if CONF_SOURCE in value: |         if CONF_SOURCE in value or CONF_PLATFORM_VERSION in value: | ||||||
|                 raise cv.Invalid( |  | ||||||
|                     "Framework version needs to be explicitly specified when custom source is used." |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|             version, source = lookups[value[CONF_VERSION]] |  | ||||||
|         else: |  | ||||||
|             version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) |  | ||||||
|             source = value.get(CONF_SOURCE, None) |  | ||||||
|  |  | ||||||
|         value[CONF_VERSION] = str(version) |  | ||||||
|         value[CONF_SOURCE] = source or _format_framework_arduino_version(version) |  | ||||||
|  |  | ||||||
|         value[CONF_PLATFORM_VERSION] = value.get( |  | ||||||
|             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: |  | ||||||
|             _LOGGER.warning( |  | ||||||
|                 "The selected Arduino framework version is not the recommended one. " |  | ||||||
|                 "If there are connectivity or build issues please remove the manual version." |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         return value |  | ||||||
|  |  | ||||||
|     lookups = { |  | ||||||
|         "dev": (cv.Version(5, 4, 2), "https://github.com/espressif/esp-idf.git"), |  | ||||||
|         "latest": (cv.Version(5, 2, 2), None), |  | ||||||
|         "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if value[CONF_VERSION] in lookups: |  | ||||||
|         if CONF_SOURCE in value: |  | ||||||
|             raise cv.Invalid( |             raise cv.Invalid( | ||||||
|                 "Framework version needs to be explicitly specified when custom source is used." |                 "Version needs to be explicitly set when a custom source or platform_version is used." | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         version, source = lookups[value[CONF_VERSION]] |         platform_lookup = PLATFORM_VERSION_LOOKUP[value[CONF_VERSION]] | ||||||
|  |         value[CONF_PLATFORM_VERSION] = _parse_platform_version(str(platform_lookup)) | ||||||
|  |  | ||||||
|  |         if value[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||||
|  |             version = ARDUINO_FRAMEWORK_VERSION_LOOKUP[value[CONF_VERSION]] | ||||||
|  |         else: | ||||||
|  |             version = ESP_IDF_FRAMEWORK_VERSION_LOOKUP[value[CONF_VERSION]] | ||||||
|     else: |     else: | ||||||
|         version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) |         version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) | ||||||
|         source = value.get(CONF_SOURCE, None) |  | ||||||
|  |  | ||||||
|     if version < cv.Version(5, 0, 0): |  | ||||||
|         raise cv.Invalid("Only ESP-IDF 5.0+ is supported.") |  | ||||||
|  |  | ||||||
|     # flag this for later *before* we set value[CONF_PLATFORM_VERSION] below |  | ||||||
|     has_platform_ver = CONF_PLATFORM_VERSION in value |  | ||||||
|  |  | ||||||
|     value[CONF_PLATFORM_VERSION] = value.get( |  | ||||||
|         CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION)) |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     if ( |  | ||||||
|         is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION]) |  | ||||||
|     ) and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X: |  | ||||||
|         raise cv.Invalid( |  | ||||||
|             f"ESP-IDF {str(version)} not supported by platformio/espressif32" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     if ( |  | ||||||
|         version in SUPPORTED_PLATFORMIO_ESP_IDF_5X |  | ||||||
|         and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X |  | ||||||
|     ) and not has_platform_ver: |  | ||||||
|         raise cv.Invalid( |  | ||||||
|             f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     if ( |  | ||||||
|         not is_platformio |  | ||||||
|         and CONF_RELEASE not in value |  | ||||||
|         and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X |  | ||||||
|     ): |  | ||||||
|         raise cv.Invalid( |  | ||||||
|             f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'" |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     value[CONF_VERSION] = str(version) |     value[CONF_VERSION] = str(version) | ||||||
|     value[CONF_SOURCE] = source or _format_framework_espidf_version( |  | ||||||
|         version, value.get(CONF_RELEASE, None), is_platformio |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     if value[CONF_SOURCE].startswith("http"): |     if value[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||||
|         # prefix is necessary or platformio will complain with a cryptic error |         if version < cv.Version(3, 0, 0): | ||||||
|         value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}" |             raise cv.Invalid("Only Arduino 3.0+ is supported.") | ||||||
|  |         recommended_version = ARDUINO_FRAMEWORK_VERSION_LOOKUP["recommended"] | ||||||
|  |         platform_lookup = ARDUINO_PLATFORM_VERSION_LOOKUP.get(version) | ||||||
|  |         value[CONF_SOURCE] = value.get( | ||||||
|  |             CONF_SOURCE, _format_framework_arduino_version(version) | ||||||
|  |         ) | ||||||
|  |     else: | ||||||
|  |         if version < cv.Version(5, 0, 0): | ||||||
|  |             raise cv.Invalid("Only ESP-IDF 5.0+ is supported.") | ||||||
|  |         recommended_version = ESP_IDF_FRAMEWORK_VERSION_LOOKUP["recommended"] | ||||||
|  |         platform_lookup = ESP_IDF_PLATFORM_VERSION_LOOKUP.get(version) | ||||||
|  |         value[CONF_SOURCE] = value.get( | ||||||
|  |             CONF_SOURCE, | ||||||
|  |             _format_framework_espidf_version(version, value.get(CONF_RELEASE, None)), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION: |     if CONF_PLATFORM_VERSION not in value: | ||||||
|  |         if platform_lookup is None: | ||||||
|  |             raise cv.Invalid( | ||||||
|  |                 "Framework version not recognized; please specify platform_version" | ||||||
|  |             ) | ||||||
|  |         value[CONF_PLATFORM_VERSION] = _parse_platform_version(str(platform_lookup)) | ||||||
|  |  | ||||||
|  |     if version != recommended_version: | ||||||
|         _LOGGER.warning( |         _LOGGER.warning( | ||||||
|             "The selected ESP-IDF framework version is not the recommended one. " |             "The selected framework version is not the recommended one. " | ||||||
|  |             "If there are connectivity or build issues please remove the manual version." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if value[CONF_PLATFORM_VERSION] != _parse_platform_version( | ||||||
|  |         str(PLATFORM_VERSION_LOOKUP["recommended"]) | ||||||
|  |     ): | ||||||
|  |         _LOGGER.warning( | ||||||
|  |             "The selected platform version is not the recommended one. " | ||||||
|             "If there are connectivity or build issues please remove the manual version." |             "If there are connectivity or build issues please remove the manual version." | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
| @@ -477,26 +423,14 @@ def _check_versions(value): | |||||||
| def _parse_platform_version(value): | def _parse_platform_version(value): | ||||||
|     try: |     try: | ||||||
|         ver = cv.Version.parse(cv.version_number(value)) |         ver = cv.Version.parse(cv.version_number(value)) | ||||||
|         if ver.major >= 50:  # a pioarduino version |         release = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}" | ||||||
|             release = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}" |         if ver.extra: | ||||||
|             if ver.extra: |             release += f"-{ver.extra}" | ||||||
|                 release += f"-{ver.extra}" |         return f"https://github.com/pioarduino/platform-espressif32/releases/download/{release}/platform-espressif32.zip" | ||||||
|             return f"https://github.com/pioarduino/platform-espressif32/releases/download/{release}/platform-espressif32.zip" |  | ||||||
|         # if platform version is a valid version constraint, prefix the default package |  | ||||||
|         cv.platformio_version_constraint(value) |  | ||||||
|         return f"platformio/espressif32@{value}" |  | ||||||
|     except cv.Invalid: |     except cv.Invalid: | ||||||
|         return value |         return value | ||||||
|  |  | ||||||
|  |  | ||||||
| def _platform_is_platformio(value): |  | ||||||
|     try: |  | ||||||
|         ver = cv.Version.parse(cv.version_number(value)) |  | ||||||
|         return ver.major < 50 |  | ||||||
|     except cv.Invalid: |  | ||||||
|         return "platformio" in value |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _detect_variant(value): | def _detect_variant(value): | ||||||
|     board = value.get(CONF_BOARD) |     board = value.get(CONF_BOARD) | ||||||
|     variant = value.get(CONF_VARIANT) |     variant = value.get(CONF_VARIANT) | ||||||
| @@ -808,6 +742,8 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     conf = config[CONF_FRAMEWORK] |     conf = config[CONF_FRAMEWORK] | ||||||
|     cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) |     cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) | ||||||
|  |     if CONF_SOURCE in conf: | ||||||
|  |         cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) | ||||||
|  |  | ||||||
|     if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: |     if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]: | ||||||
|         cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") |         cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC") | ||||||
| @@ -850,8 +786,6 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add_build_flag("-Wno-nonnull-compare") |     cg.add_build_flag("-Wno-nonnull-compare") | ||||||
|  |  | ||||||
|     cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]]) |  | ||||||
|  |  | ||||||
|     add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) |     add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True) | ||||||
|     add_idf_sdkconfig_option( |     add_idf_sdkconfig_option( | ||||||
|         f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True |         f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True | ||||||
|   | |||||||
| @@ -1,5 +1,8 @@ | |||||||
|  | from collections.abc import Callable, MutableMapping | ||||||
| from enum import Enum | from enum import Enum | ||||||
|  | import logging | ||||||
| import re | import re | ||||||
|  | from typing import Any | ||||||
|  |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| @@ -9,16 +12,19 @@ from esphome.const import ( | |||||||
|     CONF_ENABLE_ON_BOOT, |     CONF_ENABLE_ON_BOOT, | ||||||
|     CONF_ESPHOME, |     CONF_ESPHOME, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|  |     CONF_MAX_CONNECTIONS, | ||||||
|     CONF_NAME, |     CONF_NAME, | ||||||
|     CONF_NAME_ADD_MAC_SUFFIX, |     CONF_NAME_ADD_MAC_SUFFIX, | ||||||
| ) | ) | ||||||
| from esphome.core import TimePeriod | from esphome.core import CORE, TimePeriod | ||||||
| import esphome.final_validate as fv | import esphome.final_validate as fv | ||||||
|  |  | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] | CODEOWNERS = ["@jesserockz", "@Rapsssito", "@bdraco"] | ||||||
| DOMAIN = "esp32_ble" | DOMAIN = "esp32_ble" | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| class BTLoggers(Enum): | class BTLoggers(Enum): | ||||||
|     """Bluetooth logger categories available in ESP-IDF. |     """Bluetooth logger categories available in ESP-IDF. | ||||||
| @@ -127,6 +133,28 @@ CONF_DISABLE_BT_LOGS = "disable_bt_logs" | |||||||
| CONF_CONNECTION_TIMEOUT = "connection_timeout" | CONF_CONNECTION_TIMEOUT = "connection_timeout" | ||||||
| CONF_MAX_NOTIFICATIONS = "max_notifications" | CONF_MAX_NOTIFICATIONS = "max_notifications" | ||||||
|  |  | ||||||
|  | # BLE connection limits | ||||||
|  | # ESP-IDF CONFIG_BT_ACL_CONNECTIONS has range 1-9, default 4 | ||||||
|  | # Total instances: 10 (ADV + SCAN + connections) | ||||||
|  | # - ADV only: up to 9 connections | ||||||
|  | # - SCAN only: up to 9 connections | ||||||
|  | # - ADV + SCAN: up to 8 connections | ||||||
|  | DEFAULT_MAX_CONNECTIONS = 3 | ||||||
|  | IDF_MAX_CONNECTIONS = 9 | ||||||
|  |  | ||||||
|  | # Connection slot tracking keys | ||||||
|  | KEY_ESP32_BLE = "esp32_ble" | ||||||
|  | KEY_USED_CONNECTION_SLOTS = "used_connection_slots" | ||||||
|  |  | ||||||
|  | # Export for use by other components (bluetooth_proxy, etc.) | ||||||
|  | __all__ = [ | ||||||
|  |     "DEFAULT_MAX_CONNECTIONS", | ||||||
|  |     "IDF_MAX_CONNECTIONS", | ||||||
|  |     "KEY_ESP32_BLE", | ||||||
|  |     "KEY_USED_CONNECTION_SLOTS", | ||||||
|  |     "consume_connection_slots", | ||||||
|  | ] | ||||||
|  |  | ||||||
| NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] | NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] | ||||||
|  |  | ||||||
| esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") | esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") | ||||||
| @@ -183,6 +211,9 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|             cv.positive_int, |             cv.positive_int, | ||||||
|             cv.Range(min=1, max=64), |             cv.Range(min=1, max=64), | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_MAX_CONNECTIONS, default=DEFAULT_MAX_CONNECTIONS): cv.All( | ||||||
|  |             cv.positive_int, cv.Range(min=1, max=IDF_MAX_CONNECTIONS) | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
| @@ -230,6 +261,56 @@ def validate_variant(_): | |||||||
|         raise cv.Invalid(f"{variant} does not support Bluetooth") |         raise cv.Invalid(f"{variant} does not support Bluetooth") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def consume_connection_slots( | ||||||
|  |     value: int, consumer: str | ||||||
|  | ) -> Callable[[MutableMapping], MutableMapping]: | ||||||
|  |     """Reserve BLE connection slots for a component. | ||||||
|  |  | ||||||
|  |     Args: | ||||||
|  |         value: Number of connection slots to reserve | ||||||
|  |         consumer: Name of the component consuming the slots | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         A validator function that records the slot usage | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def _consume_connection_slots(config: MutableMapping) -> MutableMapping: | ||||||
|  |         data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE, {}) | ||||||
|  |         slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, []) | ||||||
|  |         slots.extend([consumer] * value) | ||||||
|  |         return config | ||||||
|  |  | ||||||
|  |     return _consume_connection_slots | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_connection_slots(max_connections: int) -> None: | ||||||
|  |     """Validate that BLE connection slots don't exceed the configured maximum.""" | ||||||
|  |     ble_data = CORE.data.get(KEY_ESP32_BLE, {}) | ||||||
|  |     used_slots = ble_data.get(KEY_USED_CONNECTION_SLOTS, []) | ||||||
|  |     num_used = len(used_slots) | ||||||
|  |  | ||||||
|  |     if num_used <= max_connections: | ||||||
|  |         return | ||||||
|  |  | ||||||
|  |     slot_users = ", ".join(used_slots) | ||||||
|  |  | ||||||
|  |     if num_used > IDF_MAX_CONNECTIONS: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"BLE components require {num_used} connection slots but maximum is {IDF_MAX_CONNECTIONS}. " | ||||||
|  |             f"Reduce the number of BLE clients. Components: {slot_users}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     _LOGGER.warning( | ||||||
|  |         "BLE components require %d connection slot(s) but only %d configured. " | ||||||
|  |         "Please set 'max_connections: %d' in the 'esp32_ble' component. " | ||||||
|  |         "Components: %s", | ||||||
|  |         num_used, | ||||||
|  |         max_connections, | ||||||
|  |         num_used, | ||||||
|  |         slot_users, | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def final_validation(config): | def final_validation(config): | ||||||
|     validate_variant(config) |     validate_variant(config) | ||||||
|     if (name := config.get(CONF_NAME)) is not None: |     if (name := config.get(CONF_NAME)) is not None: | ||||||
| @@ -245,6 +326,10 @@ def final_validation(config): | |||||||
|     # Set GATT Client/Server sdkconfig options based on which components are loaded |     # Set GATT Client/Server sdkconfig options based on which components are loaded | ||||||
|     full_config = fv.full_config.get() |     full_config = fv.full_config.get() | ||||||
|  |  | ||||||
|  |     # Validate connection slots usage | ||||||
|  |     max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS) | ||||||
|  |     validate_connection_slots(max_connections) | ||||||
|  |  | ||||||
|     # Check if BLE Server is needed |     # Check if BLE Server is needed | ||||||
|     has_ble_server = "esp32_ble_server" in full_config |     has_ble_server = "esp32_ble_server" in full_config | ||||||
|     add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server) |     add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server) | ||||||
| @@ -255,6 +340,26 @@ def final_validation(config): | |||||||
|     ) |     ) | ||||||
|     add_idf_sdkconfig_option("CONFIG_BT_GATTC_ENABLE", has_ble_client) |     add_idf_sdkconfig_option("CONFIG_BT_GATTC_ENABLE", has_ble_client) | ||||||
|  |  | ||||||
|  |     # Handle max_connections: check for deprecated location in esp32_ble_tracker | ||||||
|  |     max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS) | ||||||
|  |  | ||||||
|  |     # Use value from tracker if esp32_ble doesn't have it explicitly set (backward compat) | ||||||
|  |     if "esp32_ble_tracker" in full_config: | ||||||
|  |         tracker_config = full_config["esp32_ble_tracker"] | ||||||
|  |         if "max_connections" in tracker_config and CONF_MAX_CONNECTIONS not in config: | ||||||
|  |             max_connections = tracker_config["max_connections"] | ||||||
|  |  | ||||||
|  |     # Set CONFIG_BT_ACL_CONNECTIONS to the maximum connections needed + 1 for ADV/SCAN | ||||||
|  |     # This is the Bluedroid host stack total instance limit (range 1-9, default 4) | ||||||
|  |     # Total instances = ADV/SCAN (1) + connection slots (max_connections) | ||||||
|  |     # Shared between client (tracker/ble_client) and server | ||||||
|  |     add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", max_connections + 1) | ||||||
|  |  | ||||||
|  |     # Set controller-specific max connections for ESP32 (classic) | ||||||
|  |     # CONFIG_BTDM_CTRL_BLE_MAX_CONN is ESP32-specific controller limit (just connections, not ADV/SCAN) | ||||||
|  |     # For newer chips (C3/S3/etc), different configs are used automatically | ||||||
|  |     add_idf_sdkconfig_option("CONFIG_BTDM_CTRL_BLE_MAX_CONN", max_connections) | ||||||
|  |  | ||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -270,6 +375,10 @@ async def to_code(config): | |||||||
|         cg.add(var.set_name(name)) |         cg.add(var.set_name(name)) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     # Define max connections for use in C++ code (e.g., ble_server.h) | ||||||
|  |     max_connections = config.get(CONF_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS) | ||||||
|  |     cg.add_define("USE_ESP32_BLE_MAX_CONNECTIONS", max_connections) | ||||||
|  |  | ||||||
|     add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) |     add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) | ||||||
|     add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) |     add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -213,15 +213,17 @@ bool ESP32BLE::ble_setup_() { | |||||||
|   if (this->name_.has_value()) { |   if (this->name_.has_value()) { | ||||||
|     name = this->name_.value(); |     name = this->name_.value(); | ||||||
|     if (App.is_name_add_mac_suffix_enabled()) { |     if (App.is_name_add_mac_suffix_enabled()) { | ||||||
|       name += "-" + get_mac_address().substr(6); |       name += "-"; | ||||||
|  |       name += get_mac_address().substr(6); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     name = App.get_name(); |     name = App.get_name(); | ||||||
|     if (name.length() > 20) { |     if (name.length() > 20) { | ||||||
|       if (App.is_name_add_mac_suffix_enabled()) { |       if (App.is_name_add_mac_suffix_enabled()) { | ||||||
|         name.erase(name.begin() + 13, name.end() - 7);  // Remove characters between 13 and the mac address |         // Keep first 13 chars and last 7 chars (MAC suffix), remove middle | ||||||
|  |         name.erase(13, name.length() - 20); | ||||||
|       } else { |       } else { | ||||||
|         name = name.substr(0, 20); |         name.resize(20); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -152,7 +152,7 @@ void BLEAdvertising::loop() { | |||||||
|   if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) { |   if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) { | ||||||
|     this->stop(); |     this->stop(); | ||||||
|     this->current_adv_index_ += 1; |     this->current_adv_index_ += 1; | ||||||
|     if (this->current_adv_index_ >= this->raw_advertisements_callbacks_.size()) { |     if (static_cast<size_t>(this->current_adv_index_) >= this->raw_advertisements_callbacks_.size()) { | ||||||
|       this->current_adv_index_ = -1; |       this->current_adv_index_ = -1; | ||||||
|     } |     } | ||||||
|     this->start(); |     this->start(); | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ from esphome.const import ( | |||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
| from esphome.schema_extractors import SCHEMA_EXTRACT | from esphome.schema_extractors import SCHEMA_EXTRACT | ||||||
|  |  | ||||||
| AUTO_LOAD = ["esp32_ble", "bytebuffer", "event_emitter"] | AUTO_LOAD = ["esp32_ble", "bytebuffer"] | ||||||
| CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] | CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| DOMAIN = "esp32_ble_server" | DOMAIN = "esp32_ble_server" | ||||||
|   | |||||||
| @@ -49,7 +49,11 @@ void BLECharacteristic::notify() { | |||||||
|       this->service_->get_server()->get_connected_client_count() == 0) |       this->service_->get_server()->get_connected_client_count() == 0) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   for (auto &client : this->service_->get_server()->get_clients()) { |   const uint16_t *clients = this->service_->get_server()->get_clients(); | ||||||
|  |   uint8_t client_count = this->service_->get_server()->get_client_count(); | ||||||
|  |  | ||||||
|  |   for (uint8_t i = 0; i < client_count; i++) { | ||||||
|  |     uint16_t client = clients[i]; | ||||||
|     size_t length = this->value_.size(); |     size_t length = this->value_.size(); | ||||||
|     // Find the client in the list of clients to notify |     // Find the client in the list of clients to notify | ||||||
|     auto *entry = this->find_client_in_notify_list_(client); |     auto *entry = this->find_client_in_notify_list_(client); | ||||||
| @@ -73,7 +77,7 @@ void BLECharacteristic::notify() { | |||||||
| void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { | void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { | ||||||
|   // If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified |   // If the descriptor is the CCCD descriptor, listen to its write event to know if the client wants to be notified | ||||||
|   if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) { |   if (descriptor->get_uuid() == ESPBTUUID::from_uint16(ESP_GATT_UUID_CHAR_CLIENT_CONFIG)) { | ||||||
|     descriptor->on(BLEDescriptorEvt::VectorEvt::ON_WRITE, [this](const std::vector<uint8_t> &value, uint16_t conn_id) { |     descriptor->on_write([this](std::span<const uint8_t> value, uint16_t conn_id) { | ||||||
|       if (value.size() != 2) |       if (value.size() != 2) | ||||||
|         return; |         return; | ||||||
|       uint16_t cccd = encode_uint16(value[1], value[0]); |       uint16_t cccd = encode_uint16(value[1], value[0]); | ||||||
| @@ -121,69 +125,49 @@ bool BLECharacteristic::is_created() { | |||||||
|   if (this->state_ != CREATING_DEPENDENTS) |   if (this->state_ != CREATING_DEPENDENTS) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   bool created = true; |  | ||||||
|   for (auto *descriptor : this->descriptors_) { |   for (auto *descriptor : this->descriptors_) { | ||||||
|     created &= descriptor->is_created(); |     if (!descriptor->is_created()) | ||||||
|  |       return false; | ||||||
|   } |   } | ||||||
|   if (created) |   // All descriptors are created if we reach here | ||||||
|     this->state_ = CREATED; |   this->state_ = CREATED; | ||||||
|   return this->state_ == CREATED; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool BLECharacteristic::is_failed() { | bool BLECharacteristic::is_failed() { | ||||||
|   if (this->state_ == FAILED) |   if (this->state_ == FAILED) | ||||||
|     return true; |     return true; | ||||||
|  |  | ||||||
|   bool failed = false; |  | ||||||
|   for (auto *descriptor : this->descriptors_) { |   for (auto *descriptor : this->descriptors_) { | ||||||
|     failed |= descriptor->is_failed(); |     if (descriptor->is_failed()) { | ||||||
|  |       this->state_ = FAILED; | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLECharacteristic::set_property_bit_(esp_gatt_char_prop_t bit, bool value) { | ||||||
|  |   if (value) { | ||||||
|  |     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | bit); | ||||||
|  |   } else { | ||||||
|  |     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~bit); | ||||||
|   } |   } | ||||||
|   if (failed) |  | ||||||
|     this->state_ = FAILED; |  | ||||||
|   return this->state_ == FAILED; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void BLECharacteristic::set_broadcast_property(bool value) { | void BLECharacteristic::set_broadcast_property(bool value) { | ||||||
|   if (value) { |   this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_BROADCAST, value); | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); |  | ||||||
|   } else { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| void BLECharacteristic::set_indicate_property(bool value) { | void BLECharacteristic::set_indicate_property(bool value) { | ||||||
|   if (value) { |   this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_INDICATE, value); | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); |  | ||||||
|   } else { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| void BLECharacteristic::set_notify_property(bool value) { | void BLECharacteristic::set_notify_property(bool value) { | ||||||
|   if (value) { |   this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_NOTIFY, value); | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); |  | ||||||
|   } else { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| void BLECharacteristic::set_read_property(bool value) { |  | ||||||
|   if (value) { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); |  | ||||||
|   } else { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| void BLECharacteristic::set_write_property(bool value) { |  | ||||||
|   if (value) { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); |  | ||||||
|   } else { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | void BLECharacteristic::set_read_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_READ, value); } | ||||||
|  | void BLECharacteristic::set_write_property(bool value) { this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE, value); } | ||||||
| void BLECharacteristic::set_write_no_response_property(bool value) { | void BLECharacteristic::set_write_no_response_property(bool value) { | ||||||
|   if (value) { |   this->set_property_bit_(ESP_GATT_CHAR_PROP_BIT_WRITE_NR, value); | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); |  | ||||||
|   } else { |  | ||||||
|     this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | ||||||
| @@ -208,8 +192,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt | |||||||
|       if (!param->read.need_rsp) |       if (!param->read.need_rsp) | ||||||
|         break;  // For some reason you can request a read but not want a response |         break;  // For some reason you can request a read but not want a response | ||||||
|  |  | ||||||
|       this->EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t>::emit_(BLECharacteristicEvt::EmptyEvt::ON_READ, |       if (this->on_read_callback_) { | ||||||
|                                                                           param->read.conn_id); |         (*this->on_read_callback_)(param->read.conn_id); | ||||||
|  |       } | ||||||
|  |  | ||||||
|       uint16_t max_offset = 22; |       uint16_t max_offset = 22; | ||||||
|  |  | ||||||
| @@ -277,8 +262,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (!param->write.is_prep) { |       if (!param->write.is_prep) { | ||||||
|         this->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::emit_( |         if (this->on_write_callback_) { | ||||||
|             BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->write.conn_id); |           (*this->on_write_callback_)(this->value_, param->write.conn_id); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       break; |       break; | ||||||
| @@ -289,8 +275,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt | |||||||
|         break; |         break; | ||||||
|       this->write_event_ = false; |       this->write_event_ = false; | ||||||
|       if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { |       if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { | ||||||
|         this->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::emit_( |         if (this->on_write_callback_) { | ||||||
|             BLECharacteristicEvt::VectorEvt::ON_WRITE, this->value_, param->exec_write.conn_id); |           (*this->on_write_callback_)(this->value_, param->exec_write.conn_id); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|       esp_err_t err = |       esp_err_t err = | ||||||
|           esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); |           esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, nullptr); | ||||||
|   | |||||||
| @@ -2,10 +2,12 @@ | |||||||
|  |  | ||||||
| #include "ble_descriptor.h" | #include "ble_descriptor.h" | ||||||
| #include "esphome/components/esp32_ble/ble_uuid.h" | #include "esphome/components/esp32_ble/ble_uuid.h" | ||||||
| #include "esphome/components/event_emitter/event_emitter.h" |  | ||||||
| #include "esphome/components/bytebuffer/bytebuffer.h" | #include "esphome/components/bytebuffer/bytebuffer.h" | ||||||
|  |  | ||||||
| #include <vector> | #include <vector> | ||||||
|  | #include <span> | ||||||
|  | #include <functional> | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| @@ -22,22 +24,10 @@ namespace esp32_ble_server { | |||||||
|  |  | ||||||
| using namespace esp32_ble; | using namespace esp32_ble; | ||||||
| using namespace bytebuffer; | using namespace bytebuffer; | ||||||
| using namespace event_emitter; |  | ||||||
|  |  | ||||||
| class BLEService; | class BLEService; | ||||||
|  |  | ||||||
| namespace BLECharacteristicEvt { | class BLECharacteristic { | ||||||
| enum VectorEvt { |  | ||||||
|   ON_WRITE, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum EmptyEvt { |  | ||||||
|   ON_READ, |  | ||||||
| }; |  | ||||||
| }  // namespace BLECharacteristicEvt |  | ||||||
|  |  | ||||||
| class BLECharacteristic : public EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>, |  | ||||||
|                           public EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t> { |  | ||||||
|  public: |  public: | ||||||
|   BLECharacteristic(ESPBTUUID uuid, uint32_t properties); |   BLECharacteristic(ESPBTUUID uuid, uint32_t properties); | ||||||
|   ~BLECharacteristic(); |   ~BLECharacteristic(); | ||||||
| @@ -76,6 +66,15 @@ class BLECharacteristic : public EventEmitter<BLECharacteristicEvt::VectorEvt, s | |||||||
|   bool is_created(); |   bool is_created(); | ||||||
|   bool is_failed(); |   bool is_failed(); | ||||||
|  |  | ||||||
|  |   // Direct callback registration - only allocates when callback is set | ||||||
|  |   void on_write(std::function<void(std::span<const uint8_t>, uint16_t)> &&callback) { | ||||||
|  |     this->on_write_callback_ = | ||||||
|  |         std::make_unique<std::function<void(std::span<const uint8_t>, uint16_t)>>(std::move(callback)); | ||||||
|  |   } | ||||||
|  |   void on_read(std::function<void(uint16_t)> &&callback) { | ||||||
|  |     this->on_read_callback_ = std::make_unique<std::function<void(uint16_t)>>(std::move(callback)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool write_event_{false}; |   bool write_event_{false}; | ||||||
|   BLEService *service_{}; |   BLEService *service_{}; | ||||||
| @@ -98,6 +97,11 @@ class BLECharacteristic : public EventEmitter<BLECharacteristicEvt::VectorEvt, s | |||||||
|   void remove_client_from_notify_list_(uint16_t conn_id); |   void remove_client_from_notify_list_(uint16_t conn_id); | ||||||
|   ClientNotificationEntry *find_client_in_notify_list_(uint16_t conn_id); |   ClientNotificationEntry *find_client_in_notify_list_(uint16_t conn_id); | ||||||
|  |  | ||||||
|  |   void set_property_bit_(esp_gatt_char_prop_t bit, bool value); | ||||||
|  |  | ||||||
|  |   std::unique_ptr<std::function<void(std::span<const uint8_t>, uint16_t)>> on_write_callback_; | ||||||
|  |   std::unique_ptr<std::function<void(uint16_t)>> on_read_callback_; | ||||||
|  |  | ||||||
|   esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; |   esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; | ||||||
|  |  | ||||||
|   enum State : uint8_t { |   enum State : uint8_t { | ||||||
|   | |||||||
| @@ -74,9 +74,10 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ | |||||||
|         break; |         break; | ||||||
|       this->value_.attr_len = param->write.len; |       this->value_.attr_len = param->write.len; | ||||||
|       memcpy(this->value_.attr_value, param->write.value, param->write.len); |       memcpy(this->value_.attr_value, param->write.value, param->write.len); | ||||||
|       this->emit_(BLEDescriptorEvt::VectorEvt::ON_WRITE, |       if (this->on_write_callback_) { | ||||||
|                   std::vector<uint8_t>(param->write.value, param->write.value + param->write.len), |         (*this->on_write_callback_)(std::span<const uint8_t>(param->write.value, param->write.len), | ||||||
|                   param->write.conn_id); |                                     param->write.conn_id); | ||||||
|  |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     default: |     default: | ||||||
|   | |||||||
| @@ -1,30 +1,26 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/components/esp32_ble/ble_uuid.h" | #include "esphome/components/esp32_ble/ble_uuid.h" | ||||||
| #include "esphome/components/event_emitter/event_emitter.h" |  | ||||||
| #include "esphome/components/bytebuffer/bytebuffer.h" | #include "esphome/components/bytebuffer/bytebuffer.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include <esp_gatt_defs.h> | #include <esp_gatt_defs.h> | ||||||
| #include <esp_gatts_api.h> | #include <esp_gatts_api.h> | ||||||
|  | #include <span> | ||||||
|  | #include <functional> | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_ble_server { | namespace esp32_ble_server { | ||||||
|  |  | ||||||
| using namespace esp32_ble; | using namespace esp32_ble; | ||||||
| using namespace bytebuffer; | using namespace bytebuffer; | ||||||
| using namespace event_emitter; |  | ||||||
|  |  | ||||||
| class BLECharacteristic; | class BLECharacteristic; | ||||||
|  |  | ||||||
| namespace BLEDescriptorEvt { | // Base class for BLE descriptors | ||||||
| enum VectorEvt { | class BLEDescriptor { | ||||||
|   ON_WRITE, |  | ||||||
| }; |  | ||||||
| }  // namespace BLEDescriptorEvt |  | ||||||
|  |  | ||||||
| class BLEDescriptor : public EventEmitter<BLEDescriptorEvt::VectorEvt, std::vector<uint8_t>, uint16_t> { |  | ||||||
|  public: |  public: | ||||||
|   BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true); |   BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100, bool read = true, bool write = true); | ||||||
|   virtual ~BLEDescriptor(); |   virtual ~BLEDescriptor(); | ||||||
| @@ -39,6 +35,12 @@ class BLEDescriptor : public EventEmitter<BLEDescriptorEvt::VectorEvt, std::vect | |||||||
|   bool is_created() { return this->state_ == CREATED; } |   bool is_created() { return this->state_ == CREATED; } | ||||||
|   bool is_failed() { return this->state_ == FAILED; } |   bool is_failed() { return this->state_ == FAILED; } | ||||||
|  |  | ||||||
|  |   // Direct callback registration - only allocates when callback is set | ||||||
|  |   void on_write(std::function<void(std::span<const uint8_t>, uint16_t)> &&callback) { | ||||||
|  |     this->on_write_callback_ = | ||||||
|  |         std::make_unique<std::function<void(std::span<const uint8_t>, uint16_t)>>(std::move(callback)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   BLECharacteristic *characteristic_{nullptr}; |   BLECharacteristic *characteristic_{nullptr}; | ||||||
|   ESPBTUUID uuid_; |   ESPBTUUID uuid_; | ||||||
| @@ -46,6 +48,8 @@ class BLEDescriptor : public EventEmitter<BLEDescriptorEvt::VectorEvt, std::vect | |||||||
|  |  | ||||||
|   esp_attr_value_t value_{}; |   esp_attr_value_t value_{}; | ||||||
|  |  | ||||||
|  |   std::unique_ptr<std::function<void(std::span<const uint8_t>, uint16_t)>> on_write_callback_; | ||||||
|  |  | ||||||
|   esp_gatt_perm_t permissions_{}; |   esp_gatt_perm_t permissions_{}; | ||||||
|  |  | ||||||
|   enum State : uint8_t { |   enum State : uint8_t { | ||||||
|   | |||||||
| @@ -147,20 +147,28 @@ BLEService *BLEServer::get_service(ESPBTUUID uuid, uint8_t inst_id) { | |||||||
|   return nullptr; |   return nullptr; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void BLEServer::dispatch_callbacks_(CallbackType type, uint16_t conn_id) { | ||||||
|  |   for (auto &entry : this->callbacks_) { | ||||||
|  |     if (entry.type == type) { | ||||||
|  |       entry.callback(conn_id); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | void BLEServer::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) { | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     case ESP_GATTS_CONNECT_EVT: { |     case ESP_GATTS_CONNECT_EVT: { | ||||||
|       ESP_LOGD(TAG, "BLE Client connected"); |       ESP_LOGD(TAG, "BLE Client connected"); | ||||||
|       this->add_client_(param->connect.conn_id); |       this->add_client_(param->connect.conn_id); | ||||||
|       this->emit_(BLEServerEvt::EmptyEvt::ON_CONNECT, param->connect.conn_id); |       this->dispatch_callbacks_(CallbackType::ON_CONNECT, param->connect.conn_id); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTS_DISCONNECT_EVT: { |     case ESP_GATTS_DISCONNECT_EVT: { | ||||||
|       ESP_LOGD(TAG, "BLE Client disconnected"); |       ESP_LOGD(TAG, "BLE Client disconnected"); | ||||||
|       this->remove_client_(param->disconnect.conn_id); |       this->remove_client_(param->disconnect.conn_id); | ||||||
|       this->parent_->advertising_start(); |       this->parent_->advertising_start(); | ||||||
|       this->emit_(BLEServerEvt::EmptyEvt::ON_DISCONNECT, param->disconnect.conn_id); |       this->dispatch_callbacks_(CallbackType::ON_DISCONNECT, param->disconnect.conn_id); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case ESP_GATTS_REG_EVT: { |     case ESP_GATTS_REG_EVT: { | ||||||
| @@ -177,9 +185,38 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int8_t BLEServer::find_client_index_(uint16_t conn_id) const { | ||||||
|  |   for (uint8_t i = 0; i < this->client_count_; i++) { | ||||||
|  |     if (this->clients_[i] == conn_id) | ||||||
|  |       return i; | ||||||
|  |   } | ||||||
|  |   return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEServer::add_client_(uint16_t conn_id) { | ||||||
|  |   // Check if already in list | ||||||
|  |   if (this->find_client_index_(conn_id) >= 0) | ||||||
|  |     return; | ||||||
|  |   // Add if there's space | ||||||
|  |   if (this->client_count_ < USE_ESP32_BLE_MAX_CONNECTIONS) { | ||||||
|  |     this->clients_[this->client_count_++] = conn_id; | ||||||
|  |   } else { | ||||||
|  |     // This should never happen since max clients is known at compile time | ||||||
|  |     ESP_LOGE(TAG, "Client array full"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEServer::remove_client_(uint16_t conn_id) { | ||||||
|  |   int8_t index = this->find_client_index_(conn_id); | ||||||
|  |   if (index >= 0) { | ||||||
|  |     // Replace with last element and decrement count (client order not preserved) | ||||||
|  |     this->clients_[index] = this->clients_[--this->client_count_]; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| void BLEServer::ble_before_disabled_event_handler() { | void BLEServer::ble_before_disabled_event_handler() { | ||||||
|   // Delete all clients |   // Delete all clients | ||||||
|   this->clients_.clear(); |   this->client_count_ = 0; | ||||||
|   // Delete all services |   // Delete all services | ||||||
|   for (auto &entry : this->services_) { |   for (auto &entry : this->services_) { | ||||||
|     entry.service->do_delete(); |     entry.service->do_delete(); | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| #include <unordered_set> | #include <functional> | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| @@ -24,18 +24,7 @@ namespace esp32_ble_server { | |||||||
| using namespace esp32_ble; | using namespace esp32_ble; | ||||||
| using namespace bytebuffer; | using namespace bytebuffer; | ||||||
|  |  | ||||||
| namespace BLEServerEvt { | class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented<ESP32BLE> { | ||||||
| enum EmptyEvt { |  | ||||||
|   ON_CONNECT, |  | ||||||
|   ON_DISCONNECT, |  | ||||||
| }; |  | ||||||
| }  // namespace BLEServerEvt |  | ||||||
|  |  | ||||||
| class BLEServer : public Component, |  | ||||||
|                   public GATTsEventHandler, |  | ||||||
|                   public BLEStatusEventHandler, |  | ||||||
|                   public Parented<ESP32BLE>, |  | ||||||
|                   public EventEmitter<BLEServerEvt::EmptyEvt, uint16_t> { |  | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
| @@ -57,15 +46,34 @@ class BLEServer : public Component, | |||||||
|   void set_device_information_service(BLEService *service) { this->device_information_service_ = service; } |   void set_device_information_service(BLEService *service) { this->device_information_service_ = service; } | ||||||
|  |  | ||||||
|   esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } |   esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } | ||||||
|   uint32_t get_connected_client_count() { return this->clients_.size(); } |   uint32_t get_connected_client_count() { return this->client_count_; } | ||||||
|   const std::unordered_set<uint16_t> &get_clients() { return this->clients_; } |   const uint16_t *get_clients() const { return this->clients_; } | ||||||
|  |   uint8_t get_client_count() const { return this->client_count_; } | ||||||
|  |  | ||||||
|   void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, |   void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, | ||||||
|                            esp_ble_gatts_cb_param_t *param) override; |                            esp_ble_gatts_cb_param_t *param) override; | ||||||
|  |  | ||||||
|   void ble_before_disabled_event_handler() override; |   void ble_before_disabled_event_handler() override; | ||||||
|  |  | ||||||
|  |   // Direct callback registration - supports multiple callbacks | ||||||
|  |   void on_connect(std::function<void(uint16_t)> &&callback) { | ||||||
|  |     this->callbacks_.push_back({CallbackType::ON_CONNECT, std::move(callback)}); | ||||||
|  |   } | ||||||
|  |   void on_disconnect(std::function<void(uint16_t)> &&callback) { | ||||||
|  |     this->callbacks_.push_back({CallbackType::ON_DISCONNECT, std::move(callback)}); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   enum class CallbackType : uint8_t { | ||||||
|  |     ON_CONNECT, | ||||||
|  |     ON_DISCONNECT, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   struct CallbackEntry { | ||||||
|  |     CallbackType type; | ||||||
|  |     std::function<void(uint16_t)> callback; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   struct ServiceEntry { |   struct ServiceEntry { | ||||||
|     ESPBTUUID uuid; |     ESPBTUUID uuid; | ||||||
|     uint8_t inst_id; |     uint8_t inst_id; | ||||||
| @@ -74,14 +82,19 @@ class BLEServer : public Component, | |||||||
|  |  | ||||||
|   void restart_advertising_(); |   void restart_advertising_(); | ||||||
|  |  | ||||||
|   void add_client_(uint16_t conn_id) { this->clients_.insert(conn_id); } |   int8_t find_client_index_(uint16_t conn_id) const; | ||||||
|   void remove_client_(uint16_t conn_id) { this->clients_.erase(conn_id); } |   void add_client_(uint16_t conn_id); | ||||||
|  |   void remove_client_(uint16_t conn_id); | ||||||
|  |   void dispatch_callbacks_(CallbackType type, uint16_t conn_id); | ||||||
|  |  | ||||||
|  |   std::vector<CallbackEntry> callbacks_; | ||||||
|  |  | ||||||
|   std::vector<uint8_t> manufacturer_data_{}; |   std::vector<uint8_t> manufacturer_data_{}; | ||||||
|   esp_gatt_if_t gatts_if_{0}; |   esp_gatt_if_t gatts_if_{0}; | ||||||
|   bool registered_{false}; |   bool registered_{false}; | ||||||
|  |  | ||||||
|   std::unordered_set<uint16_t> clients_; |   uint16_t clients_[USE_ESP32_BLE_MAX_CONNECTIONS]{}; | ||||||
|  |   uint8_t client_count_{0}; | ||||||
|   std::vector<ServiceEntry> services_{}; |   std::vector<ServiceEntry> services_{}; | ||||||
|   std::vector<BLEService *> services_to_start_{}; |   std::vector<BLEService *> services_to_start_{}; | ||||||
|   BLEService *device_information_service_{}; |   BLEService *device_information_service_{}; | ||||||
|   | |||||||
| @@ -14,9 +14,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w | |||||||
|     BLECharacteristic *characteristic) { |     BLECharacteristic *characteristic) { | ||||||
|   Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger =  // NOLINT(cppcoreguidelines-owning-memory) |   Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger =  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|       new Trigger<std::vector<uint8_t>, uint16_t>(); |       new Trigger<std::vector<uint8_t>, uint16_t>(); | ||||||
|   characteristic->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::on( |   characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) { | ||||||
|       BLECharacteristicEvt::VectorEvt::ON_WRITE, |     // Convert span to vector for trigger | ||||||
|       [on_write_trigger](const std::vector<uint8_t> &data, uint16_t id) { on_write_trigger->trigger(data, id); }); |     on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id); | ||||||
|  |   }); | ||||||
|   return on_write_trigger; |   return on_write_trigger; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -25,9 +26,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w | |||||||
| Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write_trigger(BLEDescriptor *descriptor) { | Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write_trigger(BLEDescriptor *descriptor) { | ||||||
|   Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger =  // NOLINT(cppcoreguidelines-owning-memory) |   Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger =  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|       new Trigger<std::vector<uint8_t>, uint16_t>(); |       new Trigger<std::vector<uint8_t>, uint16_t>(); | ||||||
|   descriptor->on( |   descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) { | ||||||
|       BLEDescriptorEvt::VectorEvt::ON_WRITE, |     // Convert span to vector for trigger | ||||||
|       [on_write_trigger](const std::vector<uint8_t> &data, uint16_t id) { on_write_trigger->trigger(data, id); }); |     on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id); | ||||||
|  |   }); | ||||||
|   return on_write_trigger; |   return on_write_trigger; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -35,8 +37,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write | |||||||
| #ifdef USE_ESP32_BLE_SERVER_ON_CONNECT | #ifdef USE_ESP32_BLE_SERVER_ON_CONNECT | ||||||
| Trigger<uint16_t> *BLETriggers::create_server_on_connect_trigger(BLEServer *server) { | Trigger<uint16_t> *BLETriggers::create_server_on_connect_trigger(BLEServer *server) { | ||||||
|   Trigger<uint16_t> *on_connect_trigger = new Trigger<uint16_t>();  // NOLINT(cppcoreguidelines-owning-memory) |   Trigger<uint16_t> *on_connect_trigger = new Trigger<uint16_t>();  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|   server->on(BLEServerEvt::EmptyEvt::ON_CONNECT, |   server->on_connect([on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); }); | ||||||
|              [on_connect_trigger](uint16_t conn_id) { on_connect_trigger->trigger(conn_id); }); |  | ||||||
|   return on_connect_trigger; |   return on_connect_trigger; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -44,38 +45,22 @@ Trigger<uint16_t> *BLETriggers::create_server_on_connect_trigger(BLEServer *serv | |||||||
| #ifdef USE_ESP32_BLE_SERVER_ON_DISCONNECT | #ifdef USE_ESP32_BLE_SERVER_ON_DISCONNECT | ||||||
| Trigger<uint16_t> *BLETriggers::create_server_on_disconnect_trigger(BLEServer *server) { | Trigger<uint16_t> *BLETriggers::create_server_on_disconnect_trigger(BLEServer *server) { | ||||||
|   Trigger<uint16_t> *on_disconnect_trigger = new Trigger<uint16_t>();  // NOLINT(cppcoreguidelines-owning-memory) |   Trigger<uint16_t> *on_disconnect_trigger = new Trigger<uint16_t>();  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|   server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, |   server->on_disconnect([on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); }); | ||||||
|              [on_disconnect_trigger](uint16_t conn_id) { on_disconnect_trigger->trigger(conn_id); }); |  | ||||||
|   return on_disconnect_trigger; |   return on_disconnect_trigger; | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION | #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION | ||||||
| void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *characteristic, | void BLECharacteristicSetValueActionManager::set_listener(BLECharacteristic *characteristic, | ||||||
|                                                           EventEmitterListenerID listener_id, |  | ||||||
|                                                           const std::function<void()> &pre_notify_listener) { |                                                           const std::function<void()> &pre_notify_listener) { | ||||||
|   // Find and remove existing listener for this characteristic |   // Find and remove existing listener for this characteristic | ||||||
|   auto *existing = this->find_listener_(characteristic); |   auto *existing = this->find_listener_(characteristic); | ||||||
|   if (existing != nullptr) { |   if (existing != nullptr) { | ||||||
|     // Remove the previous listener |  | ||||||
|     characteristic->EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t>::off(BLECharacteristicEvt::EmptyEvt::ON_READ, |  | ||||||
|                                                                                 existing->listener_id); |  | ||||||
|     // Remove the pre-notify listener |  | ||||||
|     this->off(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, existing->pre_notify_listener_id); |  | ||||||
|     // Remove from vector |     // Remove from vector | ||||||
|     this->remove_listener_(characteristic); |     this->remove_listener_(characteristic); | ||||||
|   } |   } | ||||||
|   // Create a new listener for the pre-notify event |  | ||||||
|   EventEmitterListenerID pre_notify_listener_id = |  | ||||||
|       this->on(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, |  | ||||||
|                [pre_notify_listener, characteristic](const BLECharacteristic *evt_characteristic) { |  | ||||||
|                  // Only call the pre-notify listener if the characteristic is the one we are interested in |  | ||||||
|                  if (characteristic == evt_characteristic) { |  | ||||||
|                    pre_notify_listener(); |  | ||||||
|                  } |  | ||||||
|                }); |  | ||||||
|   // Save the entry to the vector |   // Save the entry to the vector | ||||||
|   this->listeners_.push_back({characteristic, listener_id, pre_notify_listener_id}); |   this->listeners_.push_back({characteristic, pre_notify_listener}); | ||||||
| } | } | ||||||
|  |  | ||||||
| BLECharacteristicSetValueActionManager::ListenerEntry *BLECharacteristicSetValueActionManager::find_listener_( | BLECharacteristicSetValueActionManager::ListenerEntry *BLECharacteristicSetValueActionManager::find_listener_( | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ | |||||||
| #include "ble_characteristic.h" | #include "ble_characteristic.h" | ||||||
| #include "ble_descriptor.h" | #include "ble_descriptor.h" | ||||||
|  |  | ||||||
| #include "esphome/components/event_emitter/event_emitter.h" |  | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
|  |  | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -18,10 +17,6 @@ namespace esp32_ble_server { | |||||||
| namespace esp32_ble_server_automations { | namespace esp32_ble_server_automations { | ||||||
|  |  | ||||||
| using namespace esp32_ble; | using namespace esp32_ble; | ||||||
| using namespace event_emitter; |  | ||||||
|  |  | ||||||
| // Invalid listener ID constant - 0 is used as sentinel value in EventEmitter |  | ||||||
| static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0; |  | ||||||
|  |  | ||||||
| class BLETriggers { | class BLETriggers { | ||||||
|  public: |  public: | ||||||
| @@ -41,38 +36,29 @@ class BLETriggers { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION | #ifdef USE_ESP32_BLE_SERVER_SET_VALUE_ACTION | ||||||
| enum BLECharacteristicSetValueActionEvt { |  | ||||||
|   PRE_NOTIFY, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // Class to make sure only one BLECharacteristicSetValueAction is active at a time for each characteristic | // Class to make sure only one BLECharacteristicSetValueAction is active at a time for each characteristic | ||||||
| class BLECharacteristicSetValueActionManager | class BLECharacteristicSetValueActionManager { | ||||||
|     : public EventEmitter<BLECharacteristicSetValueActionEvt, BLECharacteristic *> { |  | ||||||
|  public: |  public: | ||||||
|   // Singleton pattern |   // Singleton pattern | ||||||
|   static BLECharacteristicSetValueActionManager *get_instance() { |   static BLECharacteristicSetValueActionManager *get_instance() { | ||||||
|     static BLECharacteristicSetValueActionManager instance; |     static BLECharacteristicSetValueActionManager instance; | ||||||
|     return &instance; |     return &instance; | ||||||
|   } |   } | ||||||
|   void set_listener(BLECharacteristic *characteristic, EventEmitterListenerID listener_id, |   void set_listener(BLECharacteristic *characteristic, const std::function<void()> &pre_notify_listener); | ||||||
|                     const std::function<void()> &pre_notify_listener); |   bool has_listener(BLECharacteristic *characteristic) { return this->find_listener_(characteristic) != nullptr; } | ||||||
|   EventEmitterListenerID get_listener(BLECharacteristic *characteristic) { |   void emit_pre_notify(BLECharacteristic *characteristic) { | ||||||
|     for (const auto &entry : this->listeners_) { |     for (const auto &entry : this->listeners_) { | ||||||
|       if (entry.characteristic == characteristic) { |       if (entry.characteristic == characteristic) { | ||||||
|         return entry.listener_id; |         entry.pre_notify_listener(); | ||||||
|  |         break; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return INVALID_LISTENER_ID; |  | ||||||
|   } |  | ||||||
|   void emit_pre_notify(BLECharacteristic *characteristic) { |  | ||||||
|     this->emit_(BLECharacteristicSetValueActionEvt::PRE_NOTIFY, characteristic); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   struct ListenerEntry { |   struct ListenerEntry { | ||||||
|     BLECharacteristic *characteristic; |     BLECharacteristic *characteristic; | ||||||
|     EventEmitterListenerID listener_id; |     std::function<void()> pre_notify_listener; | ||||||
|     EventEmitterListenerID pre_notify_listener_id; |  | ||||||
|   }; |   }; | ||||||
|   std::vector<ListenerEntry> listeners_; |   std::vector<ListenerEntry> listeners_; | ||||||
|  |  | ||||||
| @@ -87,24 +73,22 @@ template<typename... Ts> class BLECharacteristicSetValueAction : public Action<T | |||||||
|   void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); } |   void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); } | ||||||
|   void play(Ts... x) override { |   void play(Ts... x) override { | ||||||
|     // If the listener is already set, do nothing |     // If the listener is already set, do nothing | ||||||
|     if (BLECharacteristicSetValueActionManager::get_instance()->get_listener(this->parent_) == this->listener_id_) |     if (BLECharacteristicSetValueActionManager::get_instance()->has_listener(this->parent_)) | ||||||
|       return; |       return; | ||||||
|     // Set initial value |     // Set initial value | ||||||
|     this->parent_->set_value(this->buffer_.value(x...)); |     this->parent_->set_value(this->buffer_.value(x...)); | ||||||
|     // Set the listener for read events |     // Set the listener for read events | ||||||
|     this->listener_id_ = this->parent_->EventEmitter<BLECharacteristicEvt::EmptyEvt, uint16_t>::on( |     this->parent_->on_read([this, x...](uint16_t id) { | ||||||
|         BLECharacteristicEvt::EmptyEvt::ON_READ, [this, x...](uint16_t id) { |       // Set the value of the characteristic every time it is read | ||||||
|           // Set the value of the characteristic every time it is read |       this->parent_->set_value(this->buffer_.value(x...)); | ||||||
|           this->parent_->set_value(this->buffer_.value(x...)); |     }); | ||||||
|         }); |  | ||||||
|     // Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic |     // Set the listener in the global manager so only one BLECharacteristicSetValueAction is set for each characteristic | ||||||
|     BLECharacteristicSetValueActionManager::get_instance()->set_listener( |     BLECharacteristicSetValueActionManager::get_instance()->set_listener( | ||||||
|         this->parent_, this->listener_id_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); |         this->parent_, [this, x...]() { this->parent_->set_value(this->buffer_.value(x...)); }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   BLECharacteristic *parent_; |   BLECharacteristic *parent_; | ||||||
|   EventEmitterListenerID listener_id_; |  | ||||||
| }; | }; | ||||||
| #endif  // USE_ESP32_BLE_SERVER_SET_VALUE_ACTION | #endif  // USE_ESP32_BLE_SERVER_SET_VALUE_ACTION | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,14 +1,13 @@ | |||||||
| from __future__ import annotations | from __future__ import annotations | ||||||
|  |  | ||||||
| from collections.abc import Callable, MutableMapping |  | ||||||
| import logging | import logging | ||||||
| from typing import Any |  | ||||||
|  |  | ||||||
| from esphome import automation | from esphome import automation | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components import esp32_ble | from esphome.components import esp32_ble | ||||||
| from esphome.components.esp32 import add_idf_sdkconfig_option | from esphome.components.esp32 import add_idf_sdkconfig_option | ||||||
| from esphome.components.esp32_ble import ( | from esphome.components.esp32_ble import ( | ||||||
|  |     IDF_MAX_CONNECTIONS, | ||||||
|     BTLoggers, |     BTLoggers, | ||||||
|     bt_uuid, |     bt_uuid, | ||||||
|     bt_uuid16_format, |     bt_uuid16_format, | ||||||
| @@ -24,6 +23,7 @@ from esphome.const import ( | |||||||
|     CONF_INTERVAL, |     CONF_INTERVAL, | ||||||
|     CONF_MAC_ADDRESS, |     CONF_MAC_ADDRESS, | ||||||
|     CONF_MANUFACTURER_ID, |     CONF_MANUFACTURER_ID, | ||||||
|  |     CONF_MAX_CONNECTIONS, | ||||||
|     CONF_ON_BLE_ADVERTISE, |     CONF_ON_BLE_ADVERTISE, | ||||||
|     CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, |     CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, | ||||||
|     CONF_ON_BLE_SERVICE_DATA_ADVERTISE, |     CONF_ON_BLE_SERVICE_DATA_ADVERTISE, | ||||||
| @@ -38,19 +38,12 @@ AUTO_LOAD = ["esp32_ble"] | |||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| CODEOWNERS = ["@bdraco"] | CODEOWNERS = ["@bdraco"] | ||||||
|  |  | ||||||
| KEY_ESP32_BLE_TRACKER = "esp32_ble_tracker" |  | ||||||
| KEY_USED_CONNECTION_SLOTS = "used_connection_slots" |  | ||||||
|  |  | ||||||
| CONF_MAX_CONNECTIONS = "max_connections" |  | ||||||
| CONF_ESP32_BLE_ID = "esp32_ble_id" | CONF_ESP32_BLE_ID = "esp32_ble_id" | ||||||
| CONF_SCAN_PARAMETERS = "scan_parameters" | CONF_SCAN_PARAMETERS = "scan_parameters" | ||||||
| CONF_WINDOW = "window" | CONF_WINDOW = "window" | ||||||
| CONF_ON_SCAN_END = "on_scan_end" | CONF_ON_SCAN_END = "on_scan_end" | ||||||
| CONF_SOFTWARE_COEXISTENCE = "software_coexistence" | CONF_SOFTWARE_COEXISTENCE = "software_coexistence" | ||||||
|  |  | ||||||
| DEFAULT_MAX_CONNECTIONS = 3 |  | ||||||
| IDF_MAX_CONNECTIONS = 9 |  | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -128,6 +121,15 @@ def validate_scan_parameters(config): | |||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_max_connections_deprecated(config: ConfigType) -> ConfigType: | ||||||
|  |     if CONF_MAX_CONNECTIONS in config: | ||||||
|  |         _LOGGER.warning( | ||||||
|  |             "The 'max_connections' option in 'esp32_ble_tracker' is deprecated. " | ||||||
|  |             "Please move it to the 'esp32_ble' component instead." | ||||||
|  |         ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| def as_hex(value): | def as_hex(value): | ||||||
|     return cg.RawExpression(f"0x{value}ULL") |     return cg.RawExpression(f"0x{value}ULL") | ||||||
|  |  | ||||||
| @@ -150,24 +152,12 @@ def as_reversed_hex_array(value): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def consume_connection_slots( |  | ||||||
|     value: int, consumer: str |  | ||||||
| ) -> Callable[[MutableMapping], MutableMapping]: |  | ||||||
|     def _consume_connection_slots(config: MutableMapping) -> MutableMapping: |  | ||||||
|         data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE_TRACKER, {}) |  | ||||||
|         slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, []) |  | ||||||
|         slots.extend([consumer] * value) |  | ||||||
|         return config |  | ||||||
|  |  | ||||||
|     return _consume_connection_slots |  | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(ESP32BLETracker), |             cv.GenerateID(): cv.declare_id(ESP32BLETracker), | ||||||
|             cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), |             cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), | ||||||
|             cv.Optional(CONF_MAX_CONNECTIONS, default=DEFAULT_MAX_CONNECTIONS): cv.All( |             cv.Optional(CONF_MAX_CONNECTIONS): cv.All( | ||||||
|                 cv.positive_int, cv.Range(min=0, max=IDF_MAX_CONNECTIONS) |                 cv.positive_int, cv.Range(min=0, max=IDF_MAX_CONNECTIONS) | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All( |             cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All( | ||||||
| @@ -224,48 +214,11 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool, |             cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool, | ||||||
|         } |         } | ||||||
|     ).extend(cv.COMPONENT_SCHEMA), |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     validate_max_connections_deprecated, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_remaining_connections(config): | FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant | ||||||
|     data: dict[str, Any] = CORE.data.get(KEY_ESP32_BLE_TRACKER, {}) |  | ||||||
|     slots: list[str] = data.get(KEY_USED_CONNECTION_SLOTS, []) |  | ||||||
|     used_slots = len(slots) |  | ||||||
|     if used_slots <= config[CONF_MAX_CONNECTIONS]: |  | ||||||
|         return config |  | ||||||
|     slot_users = ", ".join(slots) |  | ||||||
|  |  | ||||||
|     if used_slots < IDF_MAX_CONNECTIONS: |  | ||||||
|         _LOGGER.warning( |  | ||||||
|             "esp32_ble_tracker exceeded `%s`: components attempted to consume %d " |  | ||||||
|             "connection slot(s) out of available configured maximum %d connection " |  | ||||||
|             "slot(s); The system automatically increased `%s` to %d to match the " |  | ||||||
|             "number of used connection slot(s) by components: %s.", |  | ||||||
|             CONF_MAX_CONNECTIONS, |  | ||||||
|             used_slots, |  | ||||||
|             config[CONF_MAX_CONNECTIONS], |  | ||||||
|             CONF_MAX_CONNECTIONS, |  | ||||||
|             used_slots, |  | ||||||
|             slot_users, |  | ||||||
|         ) |  | ||||||
|         config[CONF_MAX_CONNECTIONS] = used_slots |  | ||||||
|         return config |  | ||||||
|  |  | ||||||
|     msg = ( |  | ||||||
|         f"esp32_ble_tracker exceeded `{CONF_MAX_CONNECTIONS}`: " |  | ||||||
|         f"components attempted to consume {used_slots} connection slot(s) " |  | ||||||
|         f"out of available configured maximum {config[CONF_MAX_CONNECTIONS]} " |  | ||||||
|         f"connection slot(s); Decrease the number of BLE clients ({slot_users})" |  | ||||||
|     ) |  | ||||||
|     if config[CONF_MAX_CONNECTIONS] < IDF_MAX_CONNECTIONS: |  | ||||||
|         msg += f" or increase {CONF_MAX_CONNECTIONS}` to {used_slots}" |  | ||||||
|     msg += f" to stay under the {IDF_MAX_CONNECTIONS} connection slot(s) limit." |  | ||||||
|     raise cv.Invalid(msg) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FINAL_VALIDATE_SCHEMA = cv.All( |  | ||||||
|     validate_remaining_connections, esp32_ble.validate_variant |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| ESP_BLE_DEVICE_SCHEMA = cv.Schema( | ESP_BLE_DEVICE_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
| @@ -345,10 +298,8 @@ async def to_code(config): | |||||||
|     # Match arduino CONFIG_BTU_TASK_STACK_SIZE |     # Match arduino CONFIG_BTU_TASK_STACK_SIZE | ||||||
|     # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 |     # https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866 | ||||||
|     add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) |     add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192) | ||||||
|     add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9) |     # Note: CONFIG_BT_ACL_CONNECTIONS and CONFIG_BTDM_CTRL_BLE_MAX_CONN are now | ||||||
|     add_idf_sdkconfig_option( |     # configured in esp32_ble component based on max_connections setting | ||||||
|         "CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS] |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     cg.add_define("USE_OTA_STATE_CALLBACK")  # To be notified when an OTA update starts |     cg.add_define("USE_OTA_STATE_CALLBACK")  # To be notified when an OTA update starts | ||||||
|     cg.add_define("USE_ESP32_BLE_CLIENT") |     cg.add_define("USE_ESP32_BLE_CLIENT") | ||||||
|   | |||||||
| @@ -67,8 +67,16 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config | |||||||
| } | } | ||||||
|  |  | ||||||
| bool ESP32Can::setup_internal() { | bool ESP32Can::setup_internal() { | ||||||
|  |   static int next_twai_ctrl_num = 0; | ||||||
|  |   if (static_cast<unsigned>(next_twai_ctrl_num) >= SOC_TWAI_CONTROLLER_NUM) { | ||||||
|  |     ESP_LOGW(TAG, "Maximum number of esp32_can components created already"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   twai_general_config_t g_config = |   twai_general_config_t g_config = | ||||||
|       TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); |       TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL); | ||||||
|  |   g_config.controller_id = next_twai_ctrl_num++; | ||||||
|   if (this->tx_queue_len_.has_value()) { |   if (this->tx_queue_len_.has_value()) { | ||||||
|     g_config.tx_queue_len = this->tx_queue_len_.value(); |     g_config.tx_queue_len = this->tx_queue_len_.value(); | ||||||
|   } |   } | ||||||
| @@ -86,14 +94,14 @@ bool ESP32Can::setup_internal() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Install TWAI driver |   // Install TWAI driver | ||||||
|   if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) { |   if (twai_driver_install_v2(&g_config, &t_config, &f_config, &(this->twai_handle_)) != ESP_OK) { | ||||||
|     // Failed to install driver |     // Failed to install driver | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Start TWAI driver |   // Start TWAI driver | ||||||
|   if (twai_start() != ESP_OK) { |   if (twai_start_v2(this->twai_handle_) != ESP_OK) { | ||||||
|     // Failed to start driver |     // Failed to start driver | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return false; |     return false; | ||||||
| @@ -102,6 +110,11 @@ bool ESP32Can::setup_internal() { | |||||||
| } | } | ||||||
|  |  | ||||||
| canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { | canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { | ||||||
|  |   if (this->twai_handle_ == nullptr) { | ||||||
|  |     // not setup yet or setup failed | ||||||
|  |     return canbus::ERROR_FAIL; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) { |   if (frame->can_data_length_code > canbus::CAN_MAX_DATA_LENGTH) { | ||||||
|     return canbus::ERROR_FAILTX; |     return canbus::ERROR_FAILTX; | ||||||
|   } |   } | ||||||
| @@ -124,7 +137,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { | |||||||
|     memcpy(message.data, frame->data, frame->can_data_length_code); |     memcpy(message.data, frame->data, frame->can_data_length_code); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (twai_transmit(&message, this->tx_enqueue_timeout_ticks_) == ESP_OK) { |   if (twai_transmit_v2(this->twai_handle_, &message, this->tx_enqueue_timeout_ticks_) == ESP_OK) { | ||||||
|     return canbus::ERROR_OK; |     return canbus::ERROR_OK; | ||||||
|   } else { |   } else { | ||||||
|     return canbus::ERROR_ALLTXBUSY; |     return canbus::ERROR_ALLTXBUSY; | ||||||
| @@ -132,9 +145,14 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) { | |||||||
| } | } | ||||||
|  |  | ||||||
| canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) { | canbus::Error ESP32Can::read_message(struct canbus::CanFrame *frame) { | ||||||
|  |   if (this->twai_handle_ == nullptr) { | ||||||
|  |     // not setup yet or setup failed | ||||||
|  |     return canbus::ERROR_FAIL; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   twai_message_t message; |   twai_message_t message; | ||||||
|  |  | ||||||
|   if (twai_receive(&message, 0) != ESP_OK) { |   if (twai_receive_v2(this->twai_handle_, &message, 0) != ESP_OK) { | ||||||
|     return canbus::ERROR_NOMSG; |     return canbus::ERROR_NOMSG; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ | |||||||
| #include "esphome/components/canbus/canbus.h" | #include "esphome/components/canbus/canbus.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | #include <driver/twai.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_can { | namespace esp32_can { | ||||||
|  |  | ||||||
| @@ -29,6 +31,7 @@ class ESP32Can : public canbus::Canbus { | |||||||
|   TickType_t tx_enqueue_timeout_ticks_{}; |   TickType_t tx_enqueue_timeout_ticks_{}; | ||||||
|   optional<uint32_t> tx_queue_len_{}; |   optional<uint32_t> tx_queue_len_{}; | ||||||
|   optional<uint32_t> rx_queue_len_{}; |   optional<uint32_t> rx_queue_len_{}; | ||||||
|  |   twai_handle_t twai_handle_{nullptr}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace esp32_can | }  // namespace esp32_can | ||||||
|   | |||||||
| @@ -38,8 +38,7 @@ void ESP32ImprovComponent::setup() { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|   global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT, |   global_ble_server->on_disconnect([this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); }); | ||||||
|                         [this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); }); |  | ||||||
|  |  | ||||||
|   // Start with loop disabled - will be enabled by start() when needed |   // Start with loop disabled - will be enabled by start() when needed | ||||||
|   this->disable_loop(); |   this->disable_loop(); | ||||||
| @@ -57,12 +56,11 @@ void ESP32ImprovComponent::setup_characteristics() { | |||||||
|   this->error_->add_descriptor(error_descriptor); |   this->error_->add_descriptor(error_descriptor); | ||||||
|  |  | ||||||
|   this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); |   this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); | ||||||
|   this->rpc_->EventEmitter<BLECharacteristicEvt::VectorEvt, std::vector<uint8_t>, uint16_t>::on( |   this->rpc_->on_write([this](std::span<const uint8_t> data, uint16_t id) { | ||||||
|       BLECharacteristicEvt::VectorEvt::ON_WRITE, [this](const std::vector<uint8_t> &data, uint16_t id) { |     if (!data.empty()) { | ||||||
|         if (!data.empty()) { |       this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); | ||||||
|           this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); |     } | ||||||
|         } |   }); | ||||||
|       }); |  | ||||||
|   BLEDescriptor *rpc_descriptor = new BLE2902(); |   BLEDescriptor *rpc_descriptor = new BLE2902(); | ||||||
|   this->rpc_->add_descriptor(rpc_descriptor); |   this->rpc_->add_descriptor(rpc_descriptor); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size | |||||||
|     if (symbols_free < RMT_SYMBOLS_PER_BYTE) { |     if (symbols_free < RMT_SYMBOLS_PER_BYTE) { | ||||||
|       return 0; |       return 0; | ||||||
|     } |     } | ||||||
|     for (int32_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) { |     for (size_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) { | ||||||
|       if (bytes[index] & (1 << (7 - i))) { |       if (bytes[index] & (1 << (7 - i))) { | ||||||
|         symbols[i] = params->bit1; |         symbols[i] = params->bit1; | ||||||
|       } else { |       } else { | ||||||
|   | |||||||
| @@ -614,24 +614,67 @@ bool ESPHomeOTAComponent::handle_auth_send_() { | |||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Generate nonce with appropriate hasher |     // Generate nonce - hasher must be created and used in same stack frame | ||||||
|     bool success = false; |     // CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS: | ||||||
|  |     // 1. Hash objects must NEVER be passed to another function (different stack frame) | ||||||
|  |     // 2. NO Variable Length Arrays (VLAs) - they corrupt the stack with hardware DMA | ||||||
|  |     // 3. All hash operations (init/add/calculate) must happen in the SAME function where object is created | ||||||
|  |     // Violating these causes truncated hash output (20 bytes instead of 32) or memory corruption. | ||||||
|  |     // | ||||||
|  |     // Buffer layout after AUTH_READ completes: | ||||||
|  |     //   [0]: auth_type (1 byte) | ||||||
|  |     //   [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND | ||||||
|  |     //   [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce | ||||||
|  |     //   [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash | ||||||
|  |  | ||||||
|  |     // Declare both hash objects in same stack frame, use pointer to select. | ||||||
|  |     // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3 | ||||||
|  |     // hardware SHA acceleration - the object must exist in this stack frame for all operations. | ||||||
|  |     // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope. | ||||||
|  | #ifdef USE_OTA_SHA256 | ||||||
|  |     sha256::SHA256 sha_hasher; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_OTA_MD5 | ||||||
|  |     md5::MD5Digest md5_hasher; | ||||||
|  | #endif | ||||||
|  |     HashBase *hasher = nullptr; | ||||||
|  |  | ||||||
| #ifdef USE_OTA_SHA256 | #ifdef USE_OTA_SHA256 | ||||||
|     if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { |     if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { | ||||||
|       sha256::SHA256 sha_hasher; |       hasher = &sha_hasher; | ||||||
|       success = this->prepare_auth_nonce_(&sha_hasher); |  | ||||||
|     } |     } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_OTA_MD5 | #ifdef USE_OTA_MD5 | ||||||
|     if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { |     if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { | ||||||
|       md5::MD5Digest md5_hasher; |       hasher = &md5_hasher; | ||||||
|       success = this->prepare_auth_nonce_(&md5_hasher); |  | ||||||
|     } |     } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|     if (!success) { |     const size_t hex_size = hasher->get_size() * 2; | ||||||
|  |     const size_t nonce_len = hasher->get_size() / 4; | ||||||
|  |     const size_t auth_buf_size = 1 + 3 * hex_size; | ||||||
|  |     this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size); | ||||||
|  |     this->auth_buf_pos_ = 0; | ||||||
|  |  | ||||||
|  |     char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1); | ||||||
|  |     if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) { | ||||||
|  |       this->log_auth_warning_(LOG_STR("Random failed")); | ||||||
|  |       this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_UNKNOWN); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     hasher->init(); | ||||||
|  |     hasher->add(buf, nonce_len); | ||||||
|  |     hasher->calculate(); | ||||||
|  |     this->auth_buf_[0] = this->auth_type_; | ||||||
|  |     hasher->get_hex(buf); | ||||||
|  |  | ||||||
|  | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||||
|  |     char log_buf[65];  // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too | ||||||
|  |     memcpy(log_buf, buf, hex_size); | ||||||
|  |     log_buf[hex_size] = '\0'; | ||||||
|  |     ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf); | ||||||
|  | #endif | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Try to write auth_type + nonce |   // Try to write auth_type + nonce | ||||||
| @@ -678,89 +721,41 @@ bool ESPHomeOTAComponent::handle_auth_read_() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // We have all the data, verify it |   // We have all the data, verify it | ||||||
|   bool matches = false; |   const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1); | ||||||
|  |   const char *cnonce = nonce + hex_size; | ||||||
|  |   const char *response = cnonce + hex_size; | ||||||
|  |  | ||||||
|  |   // CRITICAL ESP32-S3: Hash objects must stay in same stack frame (no passing to other functions). | ||||||
|  |   // Declare both hash objects in same stack frame, use pointer to select. | ||||||
|  |   // NOTE: Both objects are declared here even though only one is used. This is REQUIRED for ESP32-S3 | ||||||
|  |   // hardware SHA acceleration - the object must exist in this stack frame for all operations. | ||||||
|  |   // Do NOT try to "optimize" by creating the object inside the if block, as it would go out of scope. | ||||||
|  | #ifdef USE_OTA_SHA256 | ||||||
|  |   sha256::SHA256 sha_hasher; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_OTA_MD5 | ||||||
|  |   md5::MD5Digest md5_hasher; | ||||||
|  | #endif | ||||||
|  |   HashBase *hasher = nullptr; | ||||||
|  |  | ||||||
| #ifdef USE_OTA_SHA256 | #ifdef USE_OTA_SHA256 | ||||||
|   if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { |   if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_SHA256_AUTH) { | ||||||
|     sha256::SHA256 sha_hasher; |     hasher = &sha_hasher; | ||||||
|     matches = this->verify_hash_auth_(&sha_hasher, hex_size); |  | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
| #ifdef USE_OTA_MD5 | #ifdef USE_OTA_MD5 | ||||||
|   if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { |   if (this->auth_type_ == ota::OTA_RESPONSE_REQUEST_AUTH) { | ||||||
|     md5::MD5Digest md5_hasher; |     hasher = &md5_hasher; | ||||||
|     matches = this->verify_hash_auth_(&md5_hasher, hex_size); |  | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   if (!matches) { |  | ||||||
|     this->log_auth_warning_(LOG_STR("Password mismatch")); |  | ||||||
|     this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID); |  | ||||||
|     return false; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Authentication successful - clean up auth state |  | ||||||
|   this->cleanup_auth_(); |  | ||||||
|  |  | ||||||
|   return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool ESPHomeOTAComponent::prepare_auth_nonce_(HashBase *hasher) { |  | ||||||
|   // Calculate required buffer size using the hasher |  | ||||||
|   const size_t hex_size = hasher->get_size() * 2; |  | ||||||
|   const size_t nonce_len = hasher->get_size() / 4; |  | ||||||
|  |  | ||||||
|   // Buffer layout after AUTH_READ completes: |  | ||||||
|   //   [0]: auth_type (1 byte) |  | ||||||
|   //   [1...hex_size]: nonce (hex_size bytes) - our random nonce sent in AUTH_SEND |  | ||||||
|   //   [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce |  | ||||||
|   //   [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash |  | ||||||
|   // Total: 1 + 3*hex_size |  | ||||||
|   const size_t auth_buf_size = 1 + 3 * hex_size; |  | ||||||
|   this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size); |  | ||||||
|   this->auth_buf_pos_ = 0; |  | ||||||
|  |  | ||||||
|   // Generate nonce |  | ||||||
|   char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1); |  | ||||||
|   if (!random_bytes(reinterpret_cast<uint8_t *>(buf), nonce_len)) { |  | ||||||
|     this->log_auth_warning_(LOG_STR("Random failed")); |  | ||||||
|     this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_UNKNOWN); |  | ||||||
|     return false; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   hasher->init(); |  | ||||||
|   hasher->add(buf, nonce_len); |  | ||||||
|   hasher->calculate(); |  | ||||||
|  |  | ||||||
|   // Prepare buffer: auth_type (1 byte) + nonce (hex_size bytes) |  | ||||||
|   this->auth_buf_[0] = this->auth_type_; |  | ||||||
|   hasher->get_hex(buf); |  | ||||||
|  |  | ||||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE |  | ||||||
|   char log_buf[hex_size + 1]; |  | ||||||
|   // Log nonce for debugging |  | ||||||
|   memcpy(log_buf, buf, hex_size); |  | ||||||
|   log_buf[hex_size] = '\0'; |  | ||||||
|   ESP_LOGV(TAG, "Auth: Nonce is %s", log_buf); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   return true; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bool ESPHomeOTAComponent::verify_hash_auth_(HashBase *hasher, size_t hex_size) { |  | ||||||
|   // Get pointers to the data in the buffer (see prepare_auth_nonce_ for buffer layout) |  | ||||||
|   const char *nonce = reinterpret_cast<char *>(this->auth_buf_.get() + 1);  // Skip auth_type byte |  | ||||||
|   const char *cnonce = nonce + hex_size;                                    // CNonce immediately follows nonce |  | ||||||
|   const char *response = cnonce + hex_size;                                 // Response immediately follows cnonce |  | ||||||
|  |  | ||||||
|   // Calculate expected hash: password + nonce + cnonce |  | ||||||
|   hasher->init(); |   hasher->init(); | ||||||
|   hasher->add(this->password_.c_str(), this->password_.length()); |   hasher->add(this->password_.c_str(), this->password_.length()); | ||||||
|   hasher->add(nonce, hex_size * 2);  // Add both nonce and cnonce (contiguous in buffer) |   hasher->add(nonce, hex_size * 2);  // Add both nonce and cnonce (contiguous in buffer) | ||||||
|   hasher->calculate(); |   hasher->calculate(); | ||||||
|  |  | ||||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||||
|   char log_buf[hex_size + 1]; |   char log_buf[65];  // Fixed size for SHA256 hex (64) + null, works for MD5 (32) too | ||||||
|   // Log CNonce |   // Log CNonce | ||||||
|   memcpy(log_buf, cnonce, hex_size); |   memcpy(log_buf, cnonce, hex_size); | ||||||
|   log_buf[hex_size] = '\0'; |   log_buf[hex_size] = '\0'; | ||||||
| @@ -778,7 +773,18 @@ bool ESPHomeOTAComponent::verify_hash_auth_(HashBase *hasher, size_t hex_size) { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   // Compare response |   // Compare response | ||||||
|   return hasher->equals_hex(response); |   bool matches = hasher->equals_hex(response); | ||||||
|  |  | ||||||
|  |   if (!matches) { | ||||||
|  |     this->log_auth_warning_(LOG_STR("Password mismatch")); | ||||||
|  |     this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_AUTH_INVALID); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Authentication successful - clean up auth state | ||||||
|  |   this->cleanup_auth_(); | ||||||
|  |  | ||||||
|  |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| size_t ESPHomeOTAComponent::get_auth_hex_size_() const { | size_t ESPHomeOTAComponent::get_auth_hex_size_() const { | ||||||
|   | |||||||
| @@ -47,8 +47,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent { | |||||||
|   bool handle_auth_send_(); |   bool handle_auth_send_(); | ||||||
|   bool handle_auth_read_(); |   bool handle_auth_read_(); | ||||||
|   bool select_auth_type_(); |   bool select_auth_type_(); | ||||||
|   bool prepare_auth_nonce_(HashBase *hasher); |  | ||||||
|   bool verify_hash_auth_(HashBase *hasher, size_t hex_size); |  | ||||||
|   size_t get_auth_hex_size_() const; |   size_t get_auth_hex_size_() const; | ||||||
|   void cleanup_auth_(); |   void cleanup_auth_(); | ||||||
|   void log_auth_warning_(const LogString *msg); |   void log_auth_warning_(const LogString *msg); | ||||||
|   | |||||||
| @@ -41,17 +41,20 @@ static const char *const TAG = "ethernet"; | |||||||
|  |  | ||||||
| EthernetComponent *global_eth_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | EthernetComponent *global_eth_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
|  |  | ||||||
|  | void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) { | ||||||
|  |   ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err)); | ||||||
|  |   this->mark_failed(); | ||||||
|  | } | ||||||
|  |  | ||||||
| #define ESPHL_ERROR_CHECK(err, message) \ | #define ESPHL_ERROR_CHECK(err, message) \ | ||||||
|   if ((err) != ESP_OK) { \ |   if ((err) != ESP_OK) { \ | ||||||
|     ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ |     this->log_error_and_mark_failed_(err, message); \ | ||||||
|     this->mark_failed(); \ |  | ||||||
|     return; \ |     return; \ | ||||||
|   } |   } | ||||||
|  |  | ||||||
| #define ESPHL_ERROR_CHECK_RET(err, message, ret) \ | #define ESPHL_ERROR_CHECK_RET(err, message, ret) \ | ||||||
|   if ((err) != ESP_OK) { \ |   if ((err) != ESP_OK) { \ | ||||||
|     ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ |     this->log_error_and_mark_failed_(err, message); \ | ||||||
|     this->mark_failed(); \ |  | ||||||
|     return ret; \ |     return ret; \ | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -106,6 +106,7 @@ class EthernetComponent : public Component { | |||||||
|   void start_connect_(); |   void start_connect_(); | ||||||
|   void finish_connect_(); |   void finish_connect_(); | ||||||
|   void dump_connect_params_(); |   void dump_connect_params_(); | ||||||
|  |   void log_error_and_mark_failed_(esp_err_t err, const char *message); | ||||||
| #ifdef USE_ETHERNET_KSZ8081 | #ifdef USE_ETHERNET_KSZ8081 | ||||||
|   /// @brief Set `RMII Reference Clock Select` bit for KSZ8081. |   /// @brief Set `RMII Reference Clock Select` bit for KSZ8081. | ||||||
|   void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); |   void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); | ||||||
| @@ -162,7 +163,7 @@ class EthernetComponent : public Component { | |||||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | ||||||
| extern EthernetComponent *global_eth_component; | extern EthernetComponent *global_eth_component; | ||||||
|  |  | ||||||
| #if defined(USE_ARDUINO) || ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2) | ||||||
| extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config); | extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +0,0 @@ | |||||||
| CODEOWNERS = ["@Rapsssito"] |  | ||||||
|  |  | ||||||
| # Allows event_emitter to be configured in yaml, to allow use of the C++ api. |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = {} |  | ||||||
| @@ -1,117 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| #include <vector> |  | ||||||
| #include <functional> |  | ||||||
| #include <limits> |  | ||||||
|  |  | ||||||
| #include "esphome/core/log.h" |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace event_emitter { |  | ||||||
|  |  | ||||||
| using EventEmitterListenerID = uint32_t; |  | ||||||
| static constexpr EventEmitterListenerID INVALID_LISTENER_ID = 0; |  | ||||||
|  |  | ||||||
| // EventEmitter class that can emit events with a specific name (it is highly recommended to use an enum class for this) |  | ||||||
| // and a list of arguments. Supports multiple listeners for each event. |  | ||||||
| template<typename EvtType, typename... Args> class EventEmitter { |  | ||||||
|  public: |  | ||||||
|   EventEmitterListenerID on(EvtType event, std::function<void(Args...)> listener) { |  | ||||||
|     EventEmitterListenerID listener_id = this->get_next_id_(); |  | ||||||
|  |  | ||||||
|     // Find or create event entry |  | ||||||
|     EventEntry *entry = this->find_or_create_event_(event); |  | ||||||
|     entry->listeners.push_back({listener_id, listener}); |  | ||||||
|  |  | ||||||
|     return listener_id; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void off(EvtType event, EventEmitterListenerID id) { |  | ||||||
|     EventEntry *entry = this->find_event_(event); |  | ||||||
|     if (entry == nullptr) |  | ||||||
|       return; |  | ||||||
|  |  | ||||||
|     // Remove listener with given id |  | ||||||
|     for (auto it = entry->listeners.begin(); it != entry->listeners.end(); ++it) { |  | ||||||
|       if (it->id == id) { |  | ||||||
|         // Swap with last and pop for efficient removal |  | ||||||
|         *it = entry->listeners.back(); |  | ||||||
|         entry->listeners.pop_back(); |  | ||||||
|  |  | ||||||
|         // Remove event entry if no more listeners |  | ||||||
|         if (entry->listeners.empty()) { |  | ||||||
|           this->remove_event_(event); |  | ||||||
|         } |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|  protected: |  | ||||||
|   void emit_(EvtType event, Args... args) { |  | ||||||
|     EventEntry *entry = this->find_event_(event); |  | ||||||
|     if (entry == nullptr) |  | ||||||
|       return; |  | ||||||
|  |  | ||||||
|     // Call all listeners for this event |  | ||||||
|     for (const auto &listener : entry->listeners) { |  | ||||||
|       listener.callback(args...); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|  private: |  | ||||||
|   struct Listener { |  | ||||||
|     EventEmitterListenerID id; |  | ||||||
|     std::function<void(Args...)> callback; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   struct EventEntry { |  | ||||||
|     EvtType event; |  | ||||||
|     std::vector<Listener> listeners; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   EventEmitterListenerID get_next_id_() { |  | ||||||
|     // Simple incrementing ID, wrapping around at max |  | ||||||
|     EventEmitterListenerID next_id = (this->current_id_ + 1); |  | ||||||
|     if (next_id == INVALID_LISTENER_ID) { |  | ||||||
|       next_id = 1; |  | ||||||
|     } |  | ||||||
|     this->current_id_ = next_id; |  | ||||||
|     return this->current_id_; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   EventEntry *find_event_(EvtType event) { |  | ||||||
|     for (auto &entry : this->events_) { |  | ||||||
|       if (entry.event == event) { |  | ||||||
|         return &entry; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return nullptr; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   EventEntry *find_or_create_event_(EvtType event) { |  | ||||||
|     EventEntry *entry = this->find_event_(event); |  | ||||||
|     if (entry != nullptr) |  | ||||||
|       return entry; |  | ||||||
|  |  | ||||||
|     // Create new event entry |  | ||||||
|     this->events_.push_back({event, {}}); |  | ||||||
|     return &this->events_.back(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void remove_event_(EvtType event) { |  | ||||||
|     for (auto it = this->events_.begin(); it != this->events_.end(); ++it) { |  | ||||||
|       if (it->event == event) { |  | ||||||
|         // Swap with last and pop |  | ||||||
|         *it = this->events_.back(); |  | ||||||
|         this->events_.pop_back(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   std::vector<EventEntry> events_; |  | ||||||
|   EventEmitterListenerID current_id_ = 0; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| }  // namespace event_emitter |  | ||||||
| }  // namespace esphome |  | ||||||
| @@ -80,7 +80,7 @@ void FingerprintGrowComponent::setup() { | |||||||
|   delay(20);  // This delay guarantees the sensor will in fact be powered power. |   delay(20);  // This delay guarantees the sensor will in fact be powered power. | ||||||
|  |  | ||||||
|   if (this->check_password_()) { |   if (this->check_password_()) { | ||||||
|     if (this->new_password_ != -1) { |     if (this->new_password_ != std::numeric_limits<uint32_t>::max()) { | ||||||
|       if (this->set_password_()) |       if (this->set_password_()) | ||||||
|         return; |         return; | ||||||
|     } else { |     } else { | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
| #include "esphome/components/uart/uart.h" | #include "esphome/components/uart/uart.h" | ||||||
|  |  | ||||||
|  | #include <limits> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -177,7 +178,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic | |||||||
|   uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; |   uint8_t address_[4] = {0xFF, 0xFF, 0xFF, 0xFF}; | ||||||
|   uint16_t capacity_ = 64; |   uint16_t capacity_ = 64; | ||||||
|   uint32_t password_ = 0x0; |   uint32_t password_ = 0x0; | ||||||
|   uint32_t new_password_ = -1; |   uint32_t new_password_ = std::numeric_limits<uint32_t>::max(); | ||||||
|   GPIOPin *sensing_pin_{nullptr}; |   GPIOPin *sensing_pin_{nullptr}; | ||||||
|   GPIOPin *sensor_power_pin_{nullptr}; |   GPIOPin *sensor_power_pin_{nullptr}; | ||||||
|   uint8_t enrollment_image_ = 0; |   uint8_t enrollment_image_ = 0; | ||||||
|   | |||||||
| @@ -179,7 +179,7 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo | |||||||
|         if (b) { |         if (b) { | ||||||
|           int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset; |           int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset; | ||||||
|           auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) { |           auto draw_pixel_at = [&buff, c, y_offset, this](int16_t x, int16_t y) { | ||||||
|             if (y >= y_offset && y < y_offset + this->height_) |             if (y >= y_offset && static_cast<uint32_t>(y) < y_offset + this->height_) | ||||||
|               buff->draw_pixel_at(x, y, c); |               buff->draw_pixel_at(x, y, c); | ||||||
|           }; |           }; | ||||||
|           if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) { |           if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) { | ||||||
|   | |||||||
| @@ -116,7 +116,7 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const | |||||||
|   int number_items_fit_to_screen = 0; |   int number_items_fit_to_screen = 0; | ||||||
|   const int max_item_index = this->displayed_item_->items_size() - 1; |   const int max_item_index = this->displayed_item_->items_size() - 1; | ||||||
|  |  | ||||||
|   for (size_t i = 0; i <= max_item_index; i++) { |   for (size_t i = 0; max_item_index >= 0 && i <= static_cast<size_t>(max_item_index); i++) { | ||||||
|     const auto *item = this->displayed_item_->get_item(i); |     const auto *item = this->displayed_item_->get_item(i); | ||||||
|     const bool selected = i == this->cursor_index_; |     const bool selected = i == this->cursor_index_; | ||||||
|     const display::Rect item_dimensions = this->measure_item(display, item, bounds, selected); |     const display::Rect item_dimensions = this->measure_item(display, item, bounds, selected); | ||||||
| @@ -174,7 +174,8 @@ void GraphicalDisplayMenu::draw_menu_internal_(display::Display *display, const | |||||||
|  |  | ||||||
|   display->filled_rectangle(bounds->x, bounds->y, max_width, total_height, this->background_color_); |   display->filled_rectangle(bounds->x, bounds->y, max_width, total_height, this->background_color_); | ||||||
|   auto y_offset = bounds->y; |   auto y_offset = bounds->y; | ||||||
|   for (size_t i = first_item_index; i <= last_item_index; i++) { |   for (size_t i = static_cast<size_t>(first_item_index); | ||||||
|  |        last_item_index >= 0 && i <= static_cast<size_t>(last_item_index); i++) { | ||||||
|     const auto *item = this->displayed_item_->get_item(i); |     const auto *item = this->displayed_item_->get_item(i); | ||||||
|     const bool selected = i == this->cursor_index_; |     const bool selected = i == this->cursor_index_; | ||||||
|     display::Rect dimensions = menu_dimensions[i]; |     display::Rect dimensions = menu_dimensions[i]; | ||||||
|   | |||||||
| @@ -213,7 +213,7 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy | |||||||
|                this->real_control_packet_size_); |                this->real_control_packet_size_); | ||||||
|         this->status_message_callback_.call((const char *) data, data_size); |         this->status_message_callback_.call((const char *) data, data_size); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_); |         ESP_LOGW(TAG, "Status packet too small: %zu (should be >= %zu)", data_size, this->real_control_packet_size_); | ||||||
|       } |       } | ||||||
|       switch (this->protocol_phase_) { |       switch (this->protocol_phase_) { | ||||||
|         case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: |         case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||||
| @@ -827,7 +827,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|   size_t expected_size = |   size_t expected_size = | ||||||
|       2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_; |       2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_; | ||||||
|   if (size < expected_size) { |   if (size < expected_size) { | ||||||
|     ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size); |     ESP_LOGW(TAG, "Unexpected message size %u (expexted >= %zu)", size, expected_size); | ||||||
|     return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; |     return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||||
|   } |   } | ||||||
|   uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; |   uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; | ||||||
|   | |||||||
| @@ -178,7 +178,7 @@ class HonClimate : public HaierClimateBase { | |||||||
|   int extra_control_packet_bytes_{0}; |   int extra_control_packet_bytes_{0}; | ||||||
|   int extra_sensors_packet_bytes_{4}; |   int extra_sensors_packet_bytes_{4}; | ||||||
|   int status_message_header_size_{0}; |   int status_message_header_size_{0}; | ||||||
|   int real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)}; |   size_t real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)}; | ||||||
|   int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4}; |   int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4}; | ||||||
|   HonControlMethod control_method_; |   HonControlMethod control_method_; | ||||||
|   std::queue<haier_protocol::HaierMessage> control_messages_queue_; |   std::queue<haier_protocol::HaierMessage> control_messages_queue_; | ||||||
|   | |||||||
| @@ -7,24 +7,20 @@ namespace hdc1080 { | |||||||
|  |  | ||||||
| static const char *const TAG = "hdc1080"; | static const char *const TAG = "hdc1080"; | ||||||
|  |  | ||||||
| static const uint8_t HDC1080_ADDRESS = 0x40;  // 0b1000000 from datasheet |  | ||||||
| static const uint8_t HDC1080_CMD_CONFIGURATION = 0x02; | static const uint8_t HDC1080_CMD_CONFIGURATION = 0x02; | ||||||
| static const uint8_t HDC1080_CMD_TEMPERATURE = 0x00; | static const uint8_t HDC1080_CMD_TEMPERATURE = 0x00; | ||||||
| static const uint8_t HDC1080_CMD_HUMIDITY = 0x01; | static const uint8_t HDC1080_CMD_HUMIDITY = 0x01; | ||||||
|  |  | ||||||
| void HDC1080Component::setup() { | void HDC1080Component::setup() { | ||||||
|   const uint8_t data[2] = { |   const uint8_t config[2] = {0x00, 0x00};  // resolution 14bit for both humidity and temperature | ||||||
|       0b00000000,  // resolution 14bit for both humidity and temperature |  | ||||||
|       0b00000000   // reserved |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   if (!this->write_bytes(HDC1080_CMD_CONFIGURATION, data, 2)) { |   // if configuration fails - there is a problem | ||||||
|     // as instruction is same as powerup defaults (for now), interpret as warning if this fails |   if (this->write_register(HDC1080_CMD_CONFIGURATION, config, 2) != i2c::ERROR_OK) { | ||||||
|     ESP_LOGW(TAG, "HDC1080 initial config instruction error"); |     this->mark_failed(); | ||||||
|     this->status_set_warning(); |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void HDC1080Component::dump_config() { | void HDC1080Component::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "HDC1080:"); |   ESP_LOGCONFIG(TAG, "HDC1080:"); | ||||||
|   LOG_I2C_DEVICE(this); |   LOG_I2C_DEVICE(this); | ||||||
| @@ -35,39 +31,51 @@ void HDC1080Component::dump_config() { | |||||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_); |   LOG_SENSOR("  ", "Temperature", this->temperature_); | ||||||
|   LOG_SENSOR("  ", "Humidity", this->humidity_); |   LOG_SENSOR("  ", "Humidity", this->humidity_); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HDC1080Component::update() { | void HDC1080Component::update() { | ||||||
|   uint16_t raw_temp; |   // regardless of what sensor/s are defined in yaml configuration | ||||||
|  |   // the hdc1080 setup configuration used, requires both temperature and humidity to be read | ||||||
|  |  | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |  | ||||||
|   if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) { |   if (this->write(&HDC1080_CMD_TEMPERATURE, 1) != i2c::ERROR_OK) { | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   delay(20); |  | ||||||
|   if (this->read(reinterpret_cast<uint8_t *>(&raw_temp), 2) != i2c::ERROR_OK) { |  | ||||||
|     this->status_set_warning(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   raw_temp = i2c::i2ctohs(raw_temp); |  | ||||||
|   float temp = raw_temp * 0.0025177f - 40.0f;  // raw * 2^-16 * 165 - 40 |  | ||||||
|   this->temperature_->publish_state(temp); |  | ||||||
|  |  | ||||||
|   uint16_t raw_humidity; |   this->set_timeout(20, [this]() { | ||||||
|   if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) { |     uint16_t raw_temperature; | ||||||
|     this->status_set_warning(); |     if (this->read(reinterpret_cast<uint8_t *>(&raw_temperature), 2) != i2c::ERROR_OK) { | ||||||
|     return; |       this->status_set_warning(); | ||||||
|   } |       return; | ||||||
|   delay(20); |     } | ||||||
|   if (this->read(reinterpret_cast<uint8_t *>(&raw_humidity), 2) != i2c::ERROR_OK) { |  | ||||||
|     this->status_set_warning(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   raw_humidity = i2c::i2ctohs(raw_humidity); |  | ||||||
|   float humidity = raw_humidity * 0.001525879f;  // raw * 2^-16 * 100 |  | ||||||
|   this->humidity_->publish_state(humidity); |  | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temp, humidity); |     if (this->temperature_ != nullptr) { | ||||||
|   this->status_clear_warning(); |       raw_temperature = i2c::i2ctohs(raw_temperature); | ||||||
|  |       float temperature = raw_temperature * 0.0025177f - 40.0f;  // raw * 2^-16 * 165 - 40 | ||||||
|  |       this->temperature_->publish_state(temperature); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this->write(&HDC1080_CMD_HUMIDITY, 1) != i2c::ERROR_OK) { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     this->set_timeout(20, [this]() { | ||||||
|  |       uint16_t raw_humidity; | ||||||
|  |       if (this->read(reinterpret_cast<uint8_t *>(&raw_humidity), 2) != i2c::ERROR_OK) { | ||||||
|  |         this->status_set_warning(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (this->humidity_ != nullptr) { | ||||||
|  |         raw_humidity = i2c::i2ctohs(raw_humidity); | ||||||
|  |         float humidity = raw_humidity * 0.001525879f;  // raw * 2^-16 * 100 | ||||||
|  |         this->humidity_->publish_state(humidity); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
| } | } | ||||||
| float HDC1080Component::get_setup_priority() const { return setup_priority::DATA; } |  | ||||||
|  |  | ||||||
| }  // namespace hdc1080 | }  // namespace hdc1080 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -12,13 +12,11 @@ class HDC1080Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } |   void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } | ||||||
|   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } |   void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } | ||||||
|  |  | ||||||
|   /// Setup the sensor and check for connection. |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   /// Retrieve the latest sensor values. This operation takes approximately 16ms. |  | ||||||
|   void update() override; |   void update() override; | ||||||
|  |  | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   sensor::Sensor *temperature_{nullptr}; |   sensor::Sensor *temperature_{nullptr}; | ||||||
|   | |||||||
| @@ -377,7 +377,7 @@ void I2SAudioSpeaker::speaker_task(void *params) { | |||||||
|             this_speaker->current_stream_info_.get_bits_per_sample() <= 16) { |             this_speaker->current_stream_info_.get_bits_per_sample() <= 16) { | ||||||
|           size_t len = bytes_read / sizeof(int16_t); |           size_t len = bytes_read / sizeof(int16_t); | ||||||
|           int16_t *tmp_buf = (int16_t *) new_data; |           int16_t *tmp_buf = (int16_t *) new_data; | ||||||
|           for (int i = 0; i < len; i += 2) { |           for (size_t i = 0; i < len; i += 2) { | ||||||
|             int16_t tmp = tmp_buf[i]; |             int16_t tmp = tmp_buf[i]; | ||||||
|             tmp_buf[i] = tmp_buf[i + 1]; |             tmp_buf[i] = tmp_buf[i + 1]; | ||||||
|             tmp_buf[i + 1] = tmp; |             tmp_buf[i + 1] = tmp; | ||||||
|   | |||||||
| @@ -325,7 +325,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons | |||||||
|       // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother |       // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother | ||||||
|       this->write_array(ptr, w * h * 2); |       this->write_array(ptr, w * h * 2); | ||||||
|     } else { |     } else { | ||||||
|       for (size_t y = 0; y != h; y++) { |       for (size_t y = 0; y != static_cast<size_t>(h); y++) { | ||||||
|         this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); |         this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -349,7 +349,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons | |||||||
|         App.feed_wdt(); |         App.feed_wdt(); | ||||||
|       } |       } | ||||||
|       // end of line? Skip to the next. |       // end of line? Skip to the next. | ||||||
|       if (++pixel == w) { |       if (++pixel == static_cast<size_t>(w)) { | ||||||
|         pixel = 0; |         pixel = 0; | ||||||
|         ptr += (x_pad + x_offset) * 2; |         ptr += (x_pad + x_offset) * 2; | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -19,15 +19,19 @@ std::string build_json(const json_build_t &f) { | |||||||
|  |  | ||||||
| bool parse_json(const std::string &data, const json_parse_t &f) { | bool parse_json(const std::string &data, const json_parse_t &f) { | ||||||
|   // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson |   // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson | ||||||
|   JsonDocument doc = parse_json(data); |   JsonDocument doc = parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size()); | ||||||
|   if (doc.overflowed() || doc.isNull()) |   if (doc.overflowed() || doc.isNull()) | ||||||
|     return false; |     return false; | ||||||
|   return f(doc.as<JsonObject>()); |   return f(doc.as<JsonObject>()); | ||||||
|   // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) |   // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) | ||||||
| } | } | ||||||
|  |  | ||||||
| JsonDocument parse_json(const std::string &data) { | JsonDocument parse_json(const uint8_t *data, size_t len) { | ||||||
|   // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson |   // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson | ||||||
|  |   if (data == nullptr || len == 0) { | ||||||
|  |     ESP_LOGE(TAG, "No data to parse"); | ||||||
|  |     return JsonObject();  // return unbound object | ||||||
|  |   } | ||||||
| #ifdef USE_PSRAM | #ifdef USE_PSRAM | ||||||
|   auto doc_allocator = SpiRamAllocator(); |   auto doc_allocator = SpiRamAllocator(); | ||||||
|   JsonDocument json_document(&doc_allocator); |   JsonDocument json_document(&doc_allocator); | ||||||
| @@ -38,7 +42,7 @@ JsonDocument parse_json(const std::string &data) { | |||||||
|     ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); |     ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); | ||||||
|     return JsonObject();  // return unbound object |     return JsonObject();  // return unbound object | ||||||
|   } |   } | ||||||
|   DeserializationError err = deserializeJson(json_document, data); |   DeserializationError err = deserializeJson(json_document, data, len); | ||||||
|  |  | ||||||
|   if (err == DeserializationError::Ok) { |   if (err == DeserializationError::Ok) { | ||||||
|     return json_document; |     return json_document; | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #define ARDUINOJSON_ENABLE_STD_STRING 1  // NOLINT | #define ARDUINOJSON_ENABLE_STD_STRING 1  // NOLINT | ||||||
| @@ -49,8 +50,13 @@ std::string build_json(const json_build_t &f); | |||||||
|  |  | ||||||
| /// Parse a JSON string and run the provided json parse function if it's valid. | /// Parse a JSON string and run the provided json parse function if it's valid. | ||||||
| bool parse_json(const std::string &data, const json_parse_t &f); | bool parse_json(const std::string &data, const json_parse_t &f); | ||||||
|  |  | ||||||
| /// Parse a JSON string and return the root JsonDocument (or an unbound object on error) | /// Parse a JSON string and return the root JsonDocument (or an unbound object on error) | ||||||
| JsonDocument parse_json(const std::string &data); | JsonDocument parse_json(const uint8_t *data, size_t len); | ||||||
|  | /// Parse a JSON string and return the root JsonDocument (or an unbound object on error) | ||||||
|  | inline JsonDocument parse_json(const std::string &data) { | ||||||
|  |   return parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size()); | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Builder class for creating JSON documents without lambdas | /// Builder class for creating JSON documents without lambdas | ||||||
| class JsonBuilder { | class JsonBuilder { | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ void KamstrupKMPComponent::dump_config() { | |||||||
|   LOG_SENSOR("  ", "Flow", this->flow_sensor_); |   LOG_SENSOR("  ", "Flow", this->flow_sensor_); | ||||||
|   LOG_SENSOR("  ", "Volume", this->volume_sensor_); |   LOG_SENSOR("  ", "Volume", this->volume_sensor_); | ||||||
|  |  | ||||||
|   for (int i = 0; i < this->custom_sensors_.size(); i++) { |   for (size_t i = 0; i < this->custom_sensors_.size(); i++) { | ||||||
|     LOG_SENSOR("  ", "Custom Sensor", this->custom_sensors_[i]); |     LOG_SENSOR("  ", "Custom Sensor", this->custom_sensors_[i]); | ||||||
|     ESP_LOGCONFIG(TAG, "    Command: 0x%04X", this->custom_commands_[i]); |     ESP_LOGCONFIG(TAG, "    Command: 0x%04X", this->custom_commands_[i]); | ||||||
|   } |   } | ||||||
| @@ -268,7 +268,7 @@ void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Custom sensors |   // Custom sensors | ||||||
|   for (int i = 0; i < this->custom_commands_.size(); i++) { |   for (size_t i = 0; i < this->custom_commands_.size(); i++) { | ||||||
|     if (command == this->custom_commands_[i]) { |     if (command == this->custom_commands_[i]) { | ||||||
|       this->custom_sensors_[i]->publish_state(value); |       this->custom_sensors_[i]->publish_state(value); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ class KeyCollector : public Component { | |||||||
|   void loop() override; |   void loop() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void set_provider(key_provider::KeyProvider *provider); |   void set_provider(key_provider::KeyProvider *provider); | ||||||
|   void set_min_length(int min_length) { this->min_length_ = min_length; }; |   void set_min_length(uint32_t min_length) { this->min_length_ = min_length; }; | ||||||
|   void set_max_length(int max_length) { this->max_length_ = max_length; }; |   void set_max_length(uint32_t max_length) { this->max_length_ = max_length; }; | ||||||
|   void set_start_keys(std::string start_keys) { this->start_keys_ = std::move(start_keys); }; |   void set_start_keys(std::string start_keys) { this->start_keys_ = std::move(start_keys); }; | ||||||
|   void set_end_keys(std::string end_keys) { this->end_keys_ = std::move(end_keys); }; |   void set_end_keys(std::string end_keys) { this->end_keys_ = std::move(end_keys); }; | ||||||
|   void set_end_key_required(bool end_key_required) { this->end_key_required_ = end_key_required; }; |   void set_end_key_required(bool end_key_required) { this->end_key_required_ = end_key_required; }; | ||||||
| @@ -33,8 +33,8 @@ class KeyCollector : public Component { | |||||||
|  protected: |  protected: | ||||||
|   void key_pressed_(uint8_t key); |   void key_pressed_(uint8_t key); | ||||||
|  |  | ||||||
|   int min_length_{0}; |   uint32_t min_length_{0}; | ||||||
|   int max_length_{0}; |   uint32_t max_length_{0}; | ||||||
|   std::string start_keys_; |   std::string start_keys_; | ||||||
|   std::string end_keys_; |   std::string end_keys_; | ||||||
|   bool end_key_required_{false}; |   bool end_key_required_{false}; | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								esphome/components/lm75b/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/lm75b/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										39
									
								
								esphome/components/lm75b/lm75b.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/lm75b/lm75b.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | #include "lm75b.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace lm75b { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "lm75b"; | ||||||
|  |  | ||||||
|  | void LM75BComponent::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "LM75B:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Setting up LM75B failed!"); | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void LM75BComponent::update() { | ||||||
|  |   // Create a temporary buffer | ||||||
|  |   uint8_t buff[2]; | ||||||
|  |   if (this->read_register(LM75B_REG_TEMPERATURE, buff, 2) != i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   // Obtain combined 16-bit value | ||||||
|  |   int16_t raw_temperature = (buff[0] << 8) | buff[1]; | ||||||
|  |   // Read the 11-bit raw temperature value | ||||||
|  |   raw_temperature >>= 5; | ||||||
|  |   // Publish the temperature in °C | ||||||
|  |   this->publish_state(raw_temperature * 0.125); | ||||||
|  |   if (this->status_has_warning()) { | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace lm75b | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										19
									
								
								esphome/components/lm75b/lm75b.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/lm75b/lm75b.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace lm75b { | ||||||
|  |  | ||||||
|  | static const uint8_t LM75B_REG_TEMPERATURE = 0x00; | ||||||
|  |  | ||||||
|  | class LM75BComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { | ||||||
|  |  public: | ||||||
|  |   void dump_config() override; | ||||||
|  |   void update() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace lm75b | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										34
									
								
								esphome/components/lm75b/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/lm75b/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@beormund"] | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | lm75b_ns = cg.esphome_ns.namespace("lm75b") | ||||||
|  | LM75BComponent = lm75b_ns.class_( | ||||||
|  |     "LM75BComponent", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     sensor.sensor_schema( | ||||||
|  |         LM75BComponent, | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         accuracy_decimals=3, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x48)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await sensor.new_sensor(config) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
| @@ -5,7 +5,7 @@ | |||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "esphome/core/preferences.h" | #include "esphome/core/preferences.h" | ||||||
| #include <set> | #include <initializer_list> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace lock { | namespace lock { | ||||||
| @@ -44,16 +44,22 @@ class LockTraits { | |||||||
|   bool get_assumed_state() const { return this->assumed_state_; } |   bool get_assumed_state() const { return this->assumed_state_; } | ||||||
|   void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } |   void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } | ||||||
|  |  | ||||||
|   bool supports_state(LockState state) const { return supported_states_.count(state); } |   bool supports_state(LockState state) const { return supported_states_mask_ & (1 << state); } | ||||||
|   std::set<LockState> get_supported_states() const { return supported_states_; } |   void set_supported_states(std::initializer_list<LockState> states) { | ||||||
|   void set_supported_states(std::set<LockState> states) { supported_states_ = std::move(states); } |     supported_states_mask_ = 0; | ||||||
|   void add_supported_state(LockState state) { supported_states_.insert(state); } |     for (auto state : states) { | ||||||
|  |       supported_states_mask_ |= (1 << state); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   uint8_t get_supported_states_mask() const { return supported_states_mask_; } | ||||||
|  |   void set_supported_states_mask(uint8_t mask) { supported_states_mask_ = mask; } | ||||||
|  |   void add_supported_state(LockState state) { supported_states_mask_ |= (1 << state); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool supports_open_{false}; |   bool supports_open_{false}; | ||||||
|   bool requires_code_{false}; |   bool requires_code_{false}; | ||||||
|   bool assumed_state_{false}; |   bool assumed_state_{false}; | ||||||
|   std::set<LockState> supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED}; |   uint8_t supported_states_mask_{(1 << LOCK_STATE_NONE) | (1 << LOCK_STATE_LOCKED) | (1 << LOCK_STATE_UNLOCKED)}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** This class is used to encode all control actions on a lock device. | /** This class is used to encode all control actions on a lock device. | ||||||
|   | |||||||
| @@ -95,6 +95,7 @@ DEFAULT = "DEFAULT" | |||||||
|  |  | ||||||
| CONF_INITIAL_LEVEL = "initial_level" | CONF_INITIAL_LEVEL = "initial_level" | ||||||
| CONF_LOGGER_ID = "logger_id" | CONF_LOGGER_ID = "logger_id" | ||||||
|  | CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels" | ||||||
| CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size" | CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size" | ||||||
|  |  | ||||||
| UART_SELECTION_ESP32 = { | UART_SELECTION_ESP32 = { | ||||||
| @@ -249,6 +250,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 } |                 } | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_INITIAL_LEVEL): is_log_level, |             cv.Optional(CONF_INITIAL_LEVEL): is_log_level, | ||||||
|  |             cv.Optional(CONF_RUNTIME_TAG_LEVELS, default=False): cv.boolean, | ||||||
|             cv.Optional(CONF_ON_MESSAGE): automation.validate_automation( |             cv.Optional(CONF_ON_MESSAGE): automation.validate_automation( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoggerMessageTrigger), |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoggerMessageTrigger), | ||||||
| @@ -291,8 +293,12 @@ async def to_code(config): | |||||||
|         ) |         ) | ||||||
|     cg.add(log.pre_setup()) |     cg.add(log.pre_setup()) | ||||||
|  |  | ||||||
|     for tag, log_level in config[CONF_LOGS].items(): |     # Enable runtime tag levels if logs are configured or explicitly enabled | ||||||
|         cg.add(log.set_log_level(tag, LOG_LEVELS[log_level])) |     logs_config = config[CONF_LOGS] | ||||||
|  |     if logs_config or config[CONF_RUNTIME_TAG_LEVELS]: | ||||||
|  |         cg.add_define("USE_LOGGER_RUNTIME_TAG_LEVELS") | ||||||
|  |         for tag, log_level in logs_config.items(): | ||||||
|  |             cg.add(log.set_log_level(tag, LOG_LEVELS[log_level])) | ||||||
|  |  | ||||||
|     cg.add_define("USE_LOGGER") |     cg.add_define("USE_LOGGER") | ||||||
|     this_severity = LOG_LEVEL_SEVERITY.index(level) |     this_severity = LOG_LEVEL_SEVERITY.index(level) | ||||||
| @@ -443,6 +449,7 @@ async def logger_set_level_to_code(config, action_id, template_arg, args): | |||||||
|     level = LOG_LEVELS[config[CONF_LEVEL]] |     level = LOG_LEVELS[config[CONF_LEVEL]] | ||||||
|     logger = await cg.get_variable(config[CONF_LOGGER_ID]) |     logger = await cg.get_variable(config[CONF_LOGGER_ID]) | ||||||
|     if tag := config.get(CONF_TAG): |     if tag := config.get(CONF_TAG): | ||||||
|  |         cg.add_define("USE_LOGGER_RUNTIME_TAG_LEVELS") | ||||||
|         text = str(cg.statement(logger.set_log_level(tag, level))) |         text = str(cg.statement(logger.set_log_level(tag, level))) | ||||||
|     else: |     else: | ||||||
|         text = str(cg.statement(logger.set_log_level(level))) |         text = str(cg.statement(logger.set_log_level(level))) | ||||||
|   | |||||||
| @@ -148,9 +148,11 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas | |||||||
| #endif  // USE_STORE_LOG_STR_IN_FLASH | #endif  // USE_STORE_LOG_STR_IN_FLASH | ||||||
|  |  | ||||||
| inline uint8_t Logger::level_for(const char *tag) { | inline uint8_t Logger::level_for(const char *tag) { | ||||||
|  | #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS | ||||||
|   auto it = this->log_levels_.find(tag); |   auto it = this->log_levels_.find(tag); | ||||||
|   if (it != this->log_levels_.end()) |   if (it != this->log_levels_.end()) | ||||||
|     return it->second; |     return it->second; | ||||||
|  | #endif | ||||||
|   return this->current_level_; |   return this->current_level_; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -220,7 +222,9 @@ void Logger::process_messages_() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } | void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } | ||||||
| void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; } | #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS | ||||||
|  | void Logger::set_log_level(const char *tag, uint8_t log_level) { this->log_levels_[tag] = log_level; } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) | ||||||
| UARTSelection Logger::get_uart() const { return this->uart_; } | UARTSelection Logger::get_uart() const { return this->uart_; } | ||||||
| @@ -271,9 +275,11 @@ void Logger::dump_config() { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS | ||||||
|   for (auto &it : this->log_levels_) { |   for (auto &it : this->log_levels_) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Level for '%s': %s", it.first.c_str(), LOG_STR_ARG(LOG_LEVELS[it.second])); |     ESP_LOGCONFIG(TAG, "  Level for '%s': %s", it.first, LOG_STR_ARG(LOG_LEVELS[it.second])); | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| void Logger::set_log_level(uint8_t level) { | void Logger::set_log_level(uint8_t level) { | ||||||
|   | |||||||
| @@ -36,29 +36,38 @@ struct device; | |||||||
|  |  | ||||||
| namespace esphome::logger { | namespace esphome::logger { | ||||||
|  |  | ||||||
| // Color and letter constants for log levels | #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS | ||||||
| static const char *const LOG_LEVEL_COLORS[] = { | // Comparison function for const char* keys in log_levels_ map | ||||||
|     "",                                            // NONE | struct CStrCompare { | ||||||
|     ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED),       // ERROR |   bool operator()(const char *a, const char *b) const { return strcmp(a, b) < 0; } | ||||||
|     ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW),   // WARNING | }; | ||||||
|     ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GREEN),    // INFO | #endif | ||||||
|     ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_MAGENTA),  // CONFIG |  | ||||||
|     ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_CYAN),     // DEBUG | // ANSI color code last digit (30-38 range, store only last digit to save RAM) | ||||||
|     ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GRAY),     // VERBOSE | static constexpr char LOG_LEVEL_COLOR_DIGIT[] = { | ||||||
|     ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE),    // VERY_VERBOSE |     '\0',  // NONE | ||||||
|  |     '1',   // ERROR (31 = red) | ||||||
|  |     '3',   // WARNING (33 = yellow) | ||||||
|  |     '2',   // INFO (32 = green) | ||||||
|  |     '5',   // CONFIG (35 = magenta) | ||||||
|  |     '6',   // DEBUG (36 = cyan) | ||||||
|  |     '7',   // VERBOSE (37 = gray) | ||||||
|  |     '8',   // VERY_VERBOSE (38 = white) | ||||||
| }; | }; | ||||||
|  |  | ||||||
| static const char *const LOG_LEVEL_LETTERS[] = { | static constexpr char LOG_LEVEL_LETTER_CHARS[] = { | ||||||
|     "",    // NONE |     '\0',  // NONE | ||||||
|     "E",   // ERROR |     'E',   // ERROR | ||||||
|     "W",   // WARNING |     'W',   // WARNING | ||||||
|     "I",   // INFO |     'I',   // INFO | ||||||
|     "C",   // CONFIG |     'C',   // CONFIG | ||||||
|     "D",   // DEBUG |     'D',   // DEBUG | ||||||
|     "V",   // VERBOSE |     'V',   // VERBOSE (VERY_VERBOSE uses two 'V's) | ||||||
|     "VV",  // VERY_VERBOSE |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) | ||||||
|  | static constexpr uint16_t MAX_HEADER_SIZE = 128; | ||||||
|  |  | ||||||
| #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) | #if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) | ||||||
| /** Enum for logging UART selection | /** Enum for logging UART selection | ||||||
|  * |  * | ||||||
| @@ -131,8 +140,10 @@ class Logger : public Component { | |||||||
|  |  | ||||||
|   /// Set the default log level for this logger. |   /// Set the default log level for this logger. | ||||||
|   void set_log_level(uint8_t level); |   void set_log_level(uint8_t level); | ||||||
|  | #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS | ||||||
|   /// Set the log level of the specified tag. |   /// Set the log level of the specified tag. | ||||||
|   void set_log_level(const std::string &tag, uint8_t log_level); |   void set_log_level(const char *tag, uint8_t log_level); | ||||||
|  | #endif | ||||||
|   uint8_t get_log_level() { return this->current_level_; } |   uint8_t get_log_level() { return this->current_level_; } | ||||||
|  |  | ||||||
|   // ========== INTERNAL METHODS ========== |   // ========== INTERNAL METHODS ========== | ||||||
| @@ -215,14 +226,6 @@ class Logger : public Component { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Format string to explicit buffer with varargs |  | ||||||
|   inline void printf_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, ...) { |  | ||||||
|     va_list arg; |  | ||||||
|     va_start(arg, format); |  | ||||||
|     this->format_body_to_buffer_(buffer, buffer_at, buffer_size, format, arg); |  | ||||||
|     va_end(arg); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| #ifndef USE_HOST | #ifndef USE_HOST | ||||||
|   const LogString *get_uart_selection_(); |   const LogString *get_uart_selection_(); | ||||||
| #endif | #endif | ||||||
| @@ -248,7 +251,9 @@ class Logger : public Component { | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   // Large objects (internally aligned) |   // Large objects (internally aligned) | ||||||
|   std::map<std::string, uint8_t> log_levels_{}; | #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS | ||||||
|  |   std::map<const char *, uint8_t, CStrCompare> log_levels_{}; | ||||||
|  | #endif | ||||||
|   CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{}; |   CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{}; | ||||||
|   CallbackManager<void(uint8_t)> level_callback_{}; |   CallbackManager<void(uint8_t)> level_callback_{}; | ||||||
| #ifdef USE_ESPHOME_TASK_LOG_BUFFER | #ifdef USE_ESPHOME_TASK_LOG_BUFFER | ||||||
| @@ -318,26 +323,76 @@ class Logger : public Component { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   static inline void copy_string(char *buffer, uint16_t &pos, const char *str) { | ||||||
|  |     const size_t len = strlen(str); | ||||||
|  |     // Intentionally no null terminator, building larger string | ||||||
|  |     memcpy(buffer + pos, str, len);  // NOLINT(bugprone-not-null-terminated-result) | ||||||
|  |     pos += len; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static inline void write_ansi_color_for_level(char *buffer, uint16_t &pos, uint8_t level) { | ||||||
|  |     if (level == 0) | ||||||
|  |       return; | ||||||
|  |     // Construct ANSI escape sequence: "\033[{bold};3{color}m" | ||||||
|  |     // Example: "\033[1;31m" for ERROR (bold red) | ||||||
|  |     buffer[pos++] = '\033'; | ||||||
|  |     buffer[pos++] = '['; | ||||||
|  |     buffer[pos++] = (level == 1) ? '1' : '0';  // Only ERROR is bold | ||||||
|  |     buffer[pos++] = ';'; | ||||||
|  |     buffer[pos++] = '3'; | ||||||
|  |     buffer[pos++] = LOG_LEVEL_COLOR_DIGIT[level]; | ||||||
|  |     buffer[pos++] = 'm'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name, |   inline void HOT write_header_to_buffer_(uint8_t level, const char *tag, int line, const char *thread_name, | ||||||
|                                           char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { |                                           char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { | ||||||
|     // Format header |     uint16_t pos = *buffer_at; | ||||||
|     // uint8_t level is already bounded 0-255, just ensure it's <= 7 |     // Early return if insufficient space - intentionally don't update buffer_at to prevent partial writes | ||||||
|     if (level > 7) |     if (pos + MAX_HEADER_SIZE > buffer_size) | ||||||
|       level = 7; |       return; | ||||||
|  |  | ||||||
|     const char *color = esphome::logger::LOG_LEVEL_COLORS[level]; |     // Construct: <color>[LEVEL][tag:line]: | ||||||
|     const char *letter = esphome::logger::LOG_LEVEL_LETTERS[level]; |     write_ansi_color_for_level(buffer, pos, level); | ||||||
|  |     buffer[pos++] = '['; | ||||||
|  |     if (level != 0) { | ||||||
|  |       if (level >= 7) { | ||||||
|  |         buffer[pos++] = 'V';  // VERY_VERBOSE = "VV" | ||||||
|  |         buffer[pos++] = 'V'; | ||||||
|  |       } else { | ||||||
|  |         buffer[pos++] = LOG_LEVEL_LETTER_CHARS[level]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     buffer[pos++] = ']'; | ||||||
|  |     buffer[pos++] = '['; | ||||||
|  |     copy_string(buffer, pos, tag); | ||||||
|  |     buffer[pos++] = ':'; | ||||||
|  |     // Format line number without modulo operations (passed by value, safe to mutate) | ||||||
|  |     if (line > 999) [[unlikely]] { | ||||||
|  |       int thousands = line / 1000; | ||||||
|  |       buffer[pos++] = '0' + thousands; | ||||||
|  |       line -= thousands * 1000; | ||||||
|  |     } | ||||||
|  |     int hundreds = line / 100; | ||||||
|  |     int remainder = line - hundreds * 100; | ||||||
|  |     int tens = remainder / 10; | ||||||
|  |     buffer[pos++] = '0' + hundreds; | ||||||
|  |     buffer[pos++] = '0' + tens; | ||||||
|  |     buffer[pos++] = '0' + (remainder - tens * 10); | ||||||
|  |     buffer[pos++] = ']'; | ||||||
|  |  | ||||||
| #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) | #if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) | ||||||
|     if (thread_name != nullptr) { |     if (thread_name != nullptr) { | ||||||
|       // Non-main task with thread name |       write_ansi_color_for_level(buffer, pos, 1);  // Always use bold red for thread name | ||||||
|       this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line, |       buffer[pos++] = '['; | ||||||
|                               ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED), thread_name, color); |       copy_string(buffer, pos, thread_name); | ||||||
|       return; |       buffer[pos++] = ']'; | ||||||
|  |       write_ansi_color_for_level(buffer, pos, level);  // Restore original color | ||||||
|     } |     } | ||||||
| #endif | #endif | ||||||
|     // Main task or non ESP32/LibreTiny platform |  | ||||||
|     this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]: ", color, letter, tag, line); |     buffer[pos++] = ':'; | ||||||
|  |     buffer[pos++] = ' '; | ||||||
|  |     *buffer_at = pos; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, |   inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, | ||||||
|   | |||||||
| @@ -3,11 +3,10 @@ | |||||||
| namespace esphome::logger { | namespace esphome::logger { | ||||||
|  |  | ||||||
| void LoggerLevelSelect::publish_state(int level) { | void LoggerLevelSelect::publish_state(int level) { | ||||||
|   auto value = this->at(level); |   const auto &option = this->at(level_to_index(level)); | ||||||
|   if (!value) { |   if (!option) | ||||||
|     return; |     return; | ||||||
|   } |   Select::publish_state(option.value()); | ||||||
|   Select::publish_state(value.value()); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void LoggerLevelSelect::setup() { | void LoggerLevelSelect::setup() { | ||||||
| @@ -16,10 +15,10 @@ void LoggerLevelSelect::setup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void LoggerLevelSelect::control(const std::string &value) { | void LoggerLevelSelect::control(const std::string &value) { | ||||||
|   auto level = this->index_of(value); |   const auto index = this->index_of(value); | ||||||
|   if (!level) |   if (!index) | ||||||
|     return; |     return; | ||||||
|   this->parent_->set_log_level(level.value()); |   this->parent_->set_log_level(index_to_level(index.value())); | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace esphome::logger | }  // namespace esphome::logger | ||||||
|   | |||||||
| @@ -3,11 +3,18 @@ | |||||||
| #include "esphome/components/select/select.h" | #include "esphome/components/select/select.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/components/logger/logger.h" | #include "esphome/components/logger/logger.h" | ||||||
|  |  | ||||||
| namespace esphome::logger { | namespace esphome::logger { | ||||||
| class LoggerLevelSelect : public Component, public select::Select, public Parented<Logger> { | class LoggerLevelSelect : public Component, public select::Select, public Parented<Logger> { | ||||||
|  public: |  public: | ||||||
|   void publish_state(int level); |   void publish_state(int level); | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void control(const std::string &value) override; |   void control(const std::string &value) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   // Convert log level to option index (skip CONFIG at level 4) | ||||||
|  |   static uint8_t level_to_index(uint8_t level) { return (level > ESPHOME_LOG_LEVEL_CONFIG) ? level - 1 : level; } | ||||||
|  |   // Convert option index to log level (skip CONFIG at level 4) | ||||||
|  |   static uint8_t index_to_level(uint8_t index) { return (index >= ESPHOME_LOG_LEVEL_CONFIG) ? index + 1 : index; } | ||||||
| }; | }; | ||||||
| }  // namespace esphome::logger | }  // namespace esphome::logger | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| #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 <limits> | ||||||
|  |  | ||||||
| using esphome::i2c::ErrorCode; | using esphome::i2c::ErrorCode; | ||||||
|  |  | ||||||
| @@ -28,30 +29,30 @@ bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) { | |||||||
|  |  | ||||||
| template<typename T, size_t size> T get_next(const T (&array)[size], const T val) { | template<typename T, size_t size> T get_next(const T (&array)[size], const T val) { | ||||||
|   size_t i = 0; |   size_t i = 0; | ||||||
|   size_t idx = -1; |   size_t idx = std::numeric_limits<size_t>::max(); | ||||||
|   while (idx == -1 && i < size) { |   while (idx == std::numeric_limits<size_t>::max() && i < size) { | ||||||
|     if (array[i] == val) { |     if (array[i] == val) { | ||||||
|       idx = i; |       idx = i; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     i++; |     i++; | ||||||
|   } |   } | ||||||
|   if (idx == -1 || i + 1 >= size) |   if (idx == std::numeric_limits<size_t>::max() || i + 1 >= size) | ||||||
|     return val; |     return val; | ||||||
|   return array[i + 1]; |   return array[i + 1]; | ||||||
| } | } | ||||||
|  |  | ||||||
| template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) { | template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) { | ||||||
|   size_t i = size - 1; |   size_t i = size - 1; | ||||||
|   size_t idx = -1; |   size_t idx = std::numeric_limits<size_t>::max(); | ||||||
|   while (idx == -1 && i > 0) { |   while (idx == std::numeric_limits<size_t>::max() && i > 0) { | ||||||
|     if (array[i] == val) { |     if (array[i] == val) { | ||||||
|       idx = i; |       idx = i; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     i--; |     i--; | ||||||
|   } |   } | ||||||
|   if (idx == -1 || i == 0) |   if (idx == std::numeric_limits<size_t>::max() || i == 0) | ||||||
|     return val; |     return val; | ||||||
|   return array[i - 1]; |   return array[i - 1]; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| #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 <limits> | ||||||
|  |  | ||||||
| using esphome::i2c::ErrorCode; | using esphome::i2c::ErrorCode; | ||||||
|  |  | ||||||
| @@ -14,30 +15,30 @@ static const uint8_t MAX_TRIES = 5; | |||||||
|  |  | ||||||
| template<typename T, size_t size> T get_next(const T (&array)[size], const T val) { | template<typename T, size_t size> T get_next(const T (&array)[size], const T val) { | ||||||
|   size_t i = 0; |   size_t i = 0; | ||||||
|   size_t idx = -1; |   size_t idx = std::numeric_limits<size_t>::max(); | ||||||
|   while (idx == -1 && i < size) { |   while (idx == std::numeric_limits<size_t>::max() && i < size) { | ||||||
|     if (array[i] == val) { |     if (array[i] == val) { | ||||||
|       idx = i; |       idx = i; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     i++; |     i++; | ||||||
|   } |   } | ||||||
|   if (idx == -1 || i + 1 >= size) |   if (idx == std::numeric_limits<size_t>::max() || i + 1 >= size) | ||||||
|     return val; |     return val; | ||||||
|   return array[i + 1]; |   return array[i + 1]; | ||||||
| } | } | ||||||
|  |  | ||||||
| template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) { | template<typename T, size_t size> T get_prev(const T (&array)[size], const T val) { | ||||||
|   size_t i = size - 1; |   size_t i = size - 1; | ||||||
|   size_t idx = -1; |   size_t idx = std::numeric_limits<size_t>::max(); | ||||||
|   while (idx == -1 && i > 0) { |   while (idx == std::numeric_limits<size_t>::max() && i > 0) { | ||||||
|     if (array[i] == val) { |     if (array[i] == val) { | ||||||
|       idx = i; |       idx = i; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     i--; |     i--; | ||||||
|   } |   } | ||||||
|   if (idx == -1 || i == 0) |   if (idx == std::numeric_limits<size_t>::max() || i == 0) | ||||||
|     return val; |     return val; | ||||||
|   return array[i - 1]; |   return array[i - 1]; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,9 +29,9 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { | |||||||
|   void set_columns(std::vector<GPIOPin *> pins) { columns_ = std::move(pins); }; |   void set_columns(std::vector<GPIOPin *> pins) { columns_ = std::move(pins); }; | ||||||
|   void set_rows(std::vector<GPIOPin *> pins) { rows_ = std::move(pins); }; |   void set_rows(std::vector<GPIOPin *> pins) { rows_ = std::move(pins); }; | ||||||
|   void set_keys(std::string keys) { keys_ = std::move(keys); }; |   void set_keys(std::string keys) { keys_ = std::move(keys); }; | ||||||
|   void set_debounce_time(int debounce_time) { debounce_time_ = debounce_time; }; |   void set_debounce_time(uint32_t debounce_time) { debounce_time_ = debounce_time; }; | ||||||
|   void set_has_diodes(int has_diodes) { has_diodes_ = has_diodes; }; |   void set_has_diodes(bool has_diodes) { has_diodes_ = has_diodes; }; | ||||||
|   void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; }; |   void set_has_pulldowns(bool has_pulldowns) { has_pulldowns_ = has_pulldowns; }; | ||||||
|  |  | ||||||
|   void register_listener(MatrixKeypadListener *listener); |   void register_listener(MatrixKeypadListener *listener); | ||||||
|   void register_key_trigger(MatrixKeyTrigger *trig); |   void register_key_trigger(MatrixKeyTrigger *trig); | ||||||
| @@ -40,7 +40,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component { | |||||||
|   std::vector<GPIOPin *> rows_; |   std::vector<GPIOPin *> rows_; | ||||||
|   std::vector<GPIOPin *> columns_; |   std::vector<GPIOPin *> columns_; | ||||||
|   std::string keys_; |   std::string keys_; | ||||||
|   int debounce_time_ = 0; |   uint32_t debounce_time_ = 0; | ||||||
|   bool has_diodes_{false}; |   bool has_diodes_{false}; | ||||||
|   bool has_pulldowns_{false}; |   bool has_pulldowns_{false}; | ||||||
|   int pressed_key_ = -1; |   int pressed_key_ = -1; | ||||||
|   | |||||||
| @@ -90,7 +90,7 @@ void MAX7219Component::loop() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->scroll_mode_ == ScrollMode::STOP) { |   if (this->scroll_mode_ == ScrollMode::STOP) { | ||||||
|     if (this->stepsleft_ + get_width_internal() == first_line_size + 1) { |     if (static_cast<size_t>(this->stepsleft_ + get_width_internal()) == first_line_size + 1) { | ||||||
|       if (millis_since_last_scroll < this->scroll_dwell_) { |       if (millis_since_last_scroll < this->scroll_dwell_) { | ||||||
|         ESP_LOGVV(TAG, "Dwell time at end of string in case of stop at end. Step %d, since last scroll %d, dwell %d.", |         ESP_LOGVV(TAG, "Dwell time at end of string in case of stop at end. Step %d, since last scroll %d, dwell %d.", | ||||||
|                   this->stepsleft_, millis_since_last_scroll, this->scroll_dwell_); |                   this->stepsleft_, millis_since_last_scroll, this->scroll_dwell_); | ||||||
|   | |||||||
| @@ -17,6 +17,11 @@ from esphome.coroutine import CoroPriority | |||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEPENDENCIES = ["network"] | DEPENDENCIES = ["network"] | ||||||
|  |  | ||||||
|  | # Components that create mDNS services at runtime | ||||||
|  | # IMPORTANT: If you add a new component here, you must also update the corresponding | ||||||
|  | # #ifdef blocks in mdns_component.cpp compile_records_() method | ||||||
|  | COMPONENTS_WITH_MDNS_SERVICES = ("api", "prometheus", "web_server") | ||||||
|  |  | ||||||
| mdns_ns = cg.esphome_ns.namespace("mdns") | mdns_ns = cg.esphome_ns.namespace("mdns") | ||||||
| MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component) | MDNSComponent = mdns_ns.class_("MDNSComponent", cg.Component) | ||||||
| MDNSTXTRecord = mdns_ns.struct("MDNSTXTRecord") | MDNSTXTRecord = mdns_ns.struct("MDNSTXTRecord") | ||||||
| @@ -91,12 +96,20 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add_define("USE_MDNS") |     cg.add_define("USE_MDNS") | ||||||
|  |  | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     # Calculate compile-time service count | ||||||
|     await cg.register_component(var, config) |     service_count = sum( | ||||||
|  |         1 for key in COMPONENTS_WITH_MDNS_SERVICES if key in CORE.config | ||||||
|  |     ) + len(config[CONF_SERVICES]) | ||||||
|  |  | ||||||
|     if config[CONF_SERVICES]: |     if config[CONF_SERVICES]: | ||||||
|         cg.add_define("USE_MDNS_EXTRA_SERVICES") |         cg.add_define("USE_MDNS_EXTRA_SERVICES") | ||||||
|  |  | ||||||
|  |     # Ensure at least 1 service (fallback service) | ||||||
|  |     cg.add_define("MDNS_SERVICE_COUNT", max(1, service_count)) | ||||||
|  |  | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     for service in config[CONF_SERVICES]: |     for service in config[CONF_SERVICES]: | ||||||
|         txt = [ |         txt = [ | ||||||
|             cg.StructInitializer( |             cg.StructInitializer( | ||||||
|   | |||||||
| @@ -74,32 +74,12 @@ MDNS_STATIC_CONST_CHAR(NETWORK_THREAD, "thread"); | |||||||
| void MDNSComponent::compile_records_() { | void MDNSComponent::compile_records_() { | ||||||
|   this->hostname_ = App.get_name(); |   this->hostname_ = App.get_name(); | ||||||
|  |  | ||||||
|   // Calculate exact capacity needed for services vector |   // IMPORTANT: The #ifdef blocks below must match COMPONENTS_WITH_MDNS_SERVICES | ||||||
|   size_t services_count = 0; |   // in mdns/__init__.py. If you add a new service here, update both locations. | ||||||
| #ifdef USE_API |  | ||||||
|   if (api::global_api_server != nullptr) { |  | ||||||
|     services_count++; |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
| #ifdef USE_PROMETHEUS |  | ||||||
|   services_count++; |  | ||||||
| #endif |  | ||||||
| #ifdef USE_WEBSERVER |  | ||||||
|   services_count++; |  | ||||||
| #endif |  | ||||||
| #ifdef USE_MDNS_EXTRA_SERVICES |  | ||||||
|   services_count += this->services_extra_.size(); |  | ||||||
| #endif |  | ||||||
|   // Reserve for fallback service if needed |  | ||||||
|   if (services_count == 0) { |  | ||||||
|     services_count = 1; |  | ||||||
|   } |  | ||||||
|   this->services_.reserve(services_count); |  | ||||||
|  |  | ||||||
| #ifdef USE_API | #ifdef USE_API | ||||||
|   if (api::global_api_server != nullptr) { |   if (api::global_api_server != nullptr) { | ||||||
|     this->services_.emplace_back(); |     auto &service = this->services_.emplace_next(); | ||||||
|     auto &service = this->services_.back(); |  | ||||||
|     service.service_type = MDNS_STR(SERVICE_ESPHOMELIB); |     service.service_type = MDNS_STR(SERVICE_ESPHOMELIB); | ||||||
|     service.proto = MDNS_STR(SERVICE_TCP); |     service.proto = MDNS_STR(SERVICE_TCP); | ||||||
|     service.port = api::global_api_server->get_port(); |     service.port = api::global_api_server->get_port(); | ||||||
| @@ -178,30 +158,23 @@ void MDNSComponent::compile_records_() { | |||||||
| #endif  // USE_API | #endif  // USE_API | ||||||
|  |  | ||||||
| #ifdef USE_PROMETHEUS | #ifdef USE_PROMETHEUS | ||||||
|   this->services_.emplace_back(); |   auto &prom_service = this->services_.emplace_next(); | ||||||
|   auto &prom_service = this->services_.back(); |  | ||||||
|   prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS); |   prom_service.service_type = MDNS_STR(SERVICE_PROMETHEUS); | ||||||
|   prom_service.proto = MDNS_STR(SERVICE_TCP); |   prom_service.proto = MDNS_STR(SERVICE_TCP); | ||||||
|   prom_service.port = USE_WEBSERVER_PORT; |   prom_service.port = USE_WEBSERVER_PORT; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_WEBSERVER | #ifdef USE_WEBSERVER | ||||||
|   this->services_.emplace_back(); |   auto &web_service = this->services_.emplace_next(); | ||||||
|   auto &web_service = this->services_.back(); |  | ||||||
|   web_service.service_type = MDNS_STR(SERVICE_HTTP); |   web_service.service_type = MDNS_STR(SERVICE_HTTP); | ||||||
|   web_service.proto = MDNS_STR(SERVICE_TCP); |   web_service.proto = MDNS_STR(SERVICE_TCP); | ||||||
|   web_service.port = USE_WEBSERVER_PORT; |   web_service.port = USE_WEBSERVER_PORT; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_MDNS_EXTRA_SERVICES |  | ||||||
|   this->services_.insert(this->services_.end(), this->services_extra_.begin(), this->services_extra_.end()); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| #if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES) | #if !defined(USE_API) && !defined(USE_PROMETHEUS) && !defined(USE_WEBSERVER) && !defined(USE_MDNS_EXTRA_SERVICES) | ||||||
|   // Publish "http" service if not using native API or any other services |   // Publish "http" service if not using native API or any other services | ||||||
|   // This is just to have *some* mDNS service so that .local resolution works |   // This is just to have *some* mDNS service so that .local resolution works | ||||||
|   this->services_.emplace_back(); |   auto &fallback_service = this->services_.emplace_next(); | ||||||
|   auto &fallback_service = this->services_.back(); |  | ||||||
|   fallback_service.service_type = "_http"; |   fallback_service.service_type = "_http"; | ||||||
|   fallback_service.proto = "_tcp"; |   fallback_service.proto = "_tcp"; | ||||||
|   fallback_service.port = USE_WEBSERVER_PORT; |   fallback_service.port = USE_WEBSERVER_PORT; | ||||||
| @@ -214,7 +187,7 @@ void MDNSComponent::dump_config() { | |||||||
|                 "mDNS:\n" |                 "mDNS:\n" | ||||||
|                 "  Hostname: %s", |                 "  Hostname: %s", | ||||||
|                 this->hostname_.c_str()); |                 this->hostname_.c_str()); | ||||||
| #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE | ||||||
|   ESP_LOGV(TAG, "  Services:"); |   ESP_LOGV(TAG, "  Services:"); | ||||||
|   for (const auto &service : this->services_) { |   for (const auto &service : this->services_) { | ||||||
|     ESP_LOGV(TAG, "  - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), |     ESP_LOGV(TAG, "  - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), | ||||||
| @@ -227,8 +200,6 @@ void MDNSComponent::dump_config() { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<MDNSService> MDNSComponent::get_services() { return this->services_; } |  | ||||||
|  |  | ||||||
| }  // namespace mdns | }  // namespace mdns | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -2,13 +2,16 @@ | |||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #ifdef USE_MDNS | #ifdef USE_MDNS | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> |  | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace mdns { | namespace mdns { | ||||||
|  |  | ||||||
|  | // Service count is calculated at compile time by Python codegen | ||||||
|  | // MDNS_SERVICE_COUNT will always be defined | ||||||
|  |  | ||||||
| struct MDNSTXTRecord { | struct MDNSTXTRecord { | ||||||
|   std::string key; |   std::string key; | ||||||
|   TemplatableValue<std::string> value; |   TemplatableValue<std::string> value; | ||||||
| @@ -36,18 +39,15 @@ class MDNSComponent : public Component { | |||||||
|   float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } |   float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } | ||||||
|  |  | ||||||
| #ifdef USE_MDNS_EXTRA_SERVICES | #ifdef USE_MDNS_EXTRA_SERVICES | ||||||
|   void add_extra_service(MDNSService service) { services_extra_.push_back(std::move(service)); } |   void add_extra_service(MDNSService service) { this->services_.emplace_next() = std::move(service); } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   std::vector<MDNSService> get_services(); |   const StaticVector<MDNSService, MDNS_SERVICE_COUNT> &get_services() const { return this->services_; } | ||||||
|  |  | ||||||
|   void on_shutdown() override; |   void on_shutdown() override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
| #ifdef USE_MDNS_EXTRA_SERVICES |   StaticVector<MDNSService, MDNS_SERVICE_COUNT> services_{}; | ||||||
|   std::vector<MDNSService> services_extra_{}; |  | ||||||
| #endif |  | ||||||
|   std::vector<MDNSService> services_{}; |  | ||||||
|   std::string hostname_; |   std::string hostname_; | ||||||
|   void compile_records_(); |   void compile_records_(); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -343,11 +343,7 @@ class DriverChip: | |||||||
|             ) |             ) | ||||||
|             offset_height = native_height - height - offset_height |             offset_height = native_height - height - offset_height | ||||||
|         # Swap default dimensions if swap_xy is set, or if rotation is 90/270 and we are not using a buffer |         # Swap default dimensions if swap_xy is set, or if rotation is 90/270 and we are not using a buffer | ||||||
|         rotated = not requires_buffer(config) and config.get(CONF_ROTATION, 0) in ( |         if transform.get(CONF_SWAP_XY) is True: | ||||||
|             90, |  | ||||||
|             270, |  | ||||||
|         ) |  | ||||||
|         if transform.get(CONF_SWAP_XY) is True or rotated: |  | ||||||
|             width, height = height, width |             width, height = height, width | ||||||
|             offset_height, offset_width = offset_width, offset_height |             offset_height, offset_width = offset_width, offset_height | ||||||
|         return width, height, offset_width, offset_height |         return width, height, offset_width, offset_height | ||||||
|   | |||||||
| @@ -380,25 +380,41 @@ def get_instance(config): | |||||||
|         bus_type = BusTypes[bus_type] |         bus_type = BusTypes[bus_type] | ||||||
|     buffer_type = cg.uint8 if color_depth == 8 else cg.uint16 |     buffer_type = cg.uint8 if color_depth == 8 else cg.uint16 | ||||||
|     frac = denominator(config) |     frac = denominator(config) | ||||||
|     rotation = DISPLAY_ROTATIONS[ |     rotation = ( | ||||||
|         0 if model.rotation_as_transform(config) else config.get(CONF_ROTATION, 0) |         0 if model.rotation_as_transform(config) else config.get(CONF_ROTATION, 0) | ||||||
|     ] |     ) | ||||||
|     templateargs = [ |     templateargs = [ | ||||||
|         buffer_type, |         buffer_type, | ||||||
|         bufferpixels, |         bufferpixels, | ||||||
|         config[CONF_BYTE_ORDER] == "big_endian", |         config[CONF_BYTE_ORDER] == "big_endian", | ||||||
|         display_pixel_mode, |         display_pixel_mode, | ||||||
|         bus_type, |         bus_type, | ||||||
|         width, |  | ||||||
|         height, |  | ||||||
|         offset_width, |  | ||||||
|         offset_height, |  | ||||||
|     ] |     ] | ||||||
|     # If a buffer is required, use MipiSpiBuffer, otherwise use MipiSpi |     # If a buffer is required, use MipiSpiBuffer, otherwise use MipiSpi | ||||||
|     if requires_buffer(config): |     if requires_buffer(config): | ||||||
|         templateargs.append(rotation) |         templateargs.extend( | ||||||
|         templateargs.append(frac) |             [ | ||||||
|  |                 width, | ||||||
|  |                 height, | ||||||
|  |                 offset_width, | ||||||
|  |                 offset_height, | ||||||
|  |                 DISPLAY_ROTATIONS[rotation], | ||||||
|  |                 frac, | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|         return MipiSpiBuffer, templateargs |         return MipiSpiBuffer, templateargs | ||||||
|  |     # Swap height and width if the display is rotated 90 or 270 degrees in software | ||||||
|  |     if rotation in (90, 270): | ||||||
|  |         width, height = height, width | ||||||
|  |         offset_width, offset_height = offset_height, offset_width | ||||||
|  |     templateargs.extend( | ||||||
|  |         [ | ||||||
|  |             width, | ||||||
|  |             height, | ||||||
|  |             offset_width, | ||||||
|  |             offset_height, | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|     return MipiSpi, templateargs |     return MipiSpi, templateargs | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -340,7 +340,7 @@ class MipiSpi : public display::Display, | |||||||
|         this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8); |         this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       for (size_t y = 0; y != h; y++) { |       for (size_t y = 0; y != static_cast<size_t>(h); y++) { | ||||||
|         if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) { |         if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) { | ||||||
|           this->write_array(ptr, w); |           this->write_array(ptr, w); | ||||||
|         } else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) { |         } else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) { | ||||||
| @@ -372,8 +372,8 @@ class MipiSpi : public display::Display, | |||||||
|       uint8_t dbuffer[DISPLAYPIXEL * 48]; |       uint8_t dbuffer[DISPLAYPIXEL * 48]; | ||||||
|       uint8_t *dptr = dbuffer; |       uint8_t *dptr = dbuffer; | ||||||
|       auto stride = x_offset + w + x_pad;  // stride in pixels |       auto stride = x_offset + w + x_pad;  // stride in pixels | ||||||
|       for (size_t y = 0; y != h; y++) { |       for (size_t y = 0; y != static_cast<size_t>(h); y++) { | ||||||
|         for (size_t x = 0; x != w; x++) { |         for (size_t x = 0; x != static_cast<size_t>(w); x++) { | ||||||
|           auto color_val = ptr[y * stride + x]; |           auto color_val = ptr[y * stride + x]; | ||||||
|           if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) { |           if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) { | ||||||
|             // 16 to 18 bit conversion |             // 16 to 18 bit conversion | ||||||
|   | |||||||
| @@ -572,7 +572,7 @@ void MixerSpeaker::audio_mixer_task(void *params) { | |||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       // Determine how many frames to mix |       // Determine how many frames to mix | ||||||
|       for (int i = 0; i < transfer_buffers_with_data.size(); ++i) { |       for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) { | ||||||
|         const uint32_t frames_available_in_buffer = |         const uint32_t frames_available_in_buffer = | ||||||
|             speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available()); |             speakers_with_data[i]->get_audio_stream_info().bytes_to_frames(transfer_buffers_with_data[i]->available()); | ||||||
|         frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer); |         frames_to_mix = std::min(frames_to_mix, frames_available_in_buffer); | ||||||
| @@ -581,7 +581,7 @@ void MixerSpeaker::audio_mixer_task(void *params) { | |||||||
|       audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info(); |       audio::AudioStreamInfo primary_stream_info = speakers_with_data[0]->get_audio_stream_info(); | ||||||
|  |  | ||||||
|       // Mix two streams together |       // Mix two streams together | ||||||
|       for (int i = 1; i < transfer_buffers_with_data.size(); ++i) { |       for (size_t i = 1; i < transfer_buffers_with_data.size(); ++i) { | ||||||
|         mix_audio_samples(primary_buffer, primary_stream_info, |         mix_audio_samples(primary_buffer, primary_stream_info, | ||||||
|                           reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()), |                           reinterpret_cast<int16_t *>(transfer_buffers_with_data[i]->get_buffer_start()), | ||||||
|                           speakers_with_data[i]->get_audio_stream_info(), |                           speakers_with_data[i]->get_audio_stream_info(), | ||||||
| @@ -596,7 +596,7 @@ void MixerSpeaker::audio_mixer_task(void *params) { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       // Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks |       // Update source transfer buffer lengths and add new audio durations to the source speaker pending playbacks | ||||||
|       for (int i = 0; i < transfer_buffers_with_data.size(); ++i) { |       for (size_t i = 0; i < transfer_buffers_with_data.size(); ++i) { | ||||||
|         transfer_buffers_with_data[i]->decrease_buffer_length( |         transfer_buffers_with_data[i]->decrease_buffer_length( | ||||||
|             speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix)); |             speakers_with_data[i]->get_audio_stream_info().frames_to_bytes(frames_to_mix)); | ||||||
|         speakers_with_data[i]->pending_playback_frames_ += frames_to_mix; |         speakers_with_data[i]->pending_playback_frames_ += frames_to_mix; | ||||||
|   | |||||||
| @@ -11,47 +11,49 @@ namespace mpr121 { | |||||||
| static const char *const TAG = "mpr121"; | static const char *const TAG = "mpr121"; | ||||||
|  |  | ||||||
| void MPR121Component::setup() { | void MPR121Component::setup() { | ||||||
|  |   this->disable_loop(); | ||||||
|   // soft reset device |   // soft reset device | ||||||
|   this->write_byte(MPR121_SOFTRESET, 0x63); |   this->write_byte(MPR121_SOFTRESET, 0x63); | ||||||
|   delay(100);  // NOLINT |   this->set_timeout(100, [this]() { | ||||||
|   if (!this->write_byte(MPR121_ECR, 0x0)) { |     if (!this->write_byte(MPR121_ECR, 0x0)) { | ||||||
|     this->error_code_ = COMMUNICATION_FAILED; |       this->error_code_ = COMMUNICATION_FAILED; | ||||||
|     this->mark_failed(); |       this->mark_failed(); | ||||||
|     return; |       return; | ||||||
|   } |     } | ||||||
|  |     // set touch sensitivity for all 12 channels | ||||||
|  |     for (auto *channel : this->channels_) { | ||||||
|  |       channel->setup(); | ||||||
|  |     } | ||||||
|  |     this->write_byte(MPR121_MHDR, 0x01); | ||||||
|  |     this->write_byte(MPR121_NHDR, 0x01); | ||||||
|  |     this->write_byte(MPR121_NCLR, 0x0E); | ||||||
|  |     this->write_byte(MPR121_FDLR, 0x00); | ||||||
|  |  | ||||||
|   // set touch sensitivity for all 12 channels |     this->write_byte(MPR121_MHDF, 0x01); | ||||||
|   for (auto *channel : this->channels_) { |     this->write_byte(MPR121_NHDF, 0x05); | ||||||
|     channel->setup(); |     this->write_byte(MPR121_NCLF, 0x01); | ||||||
|   } |     this->write_byte(MPR121_FDLF, 0x00); | ||||||
|   this->write_byte(MPR121_MHDR, 0x01); |  | ||||||
|   this->write_byte(MPR121_NHDR, 0x01); |  | ||||||
|   this->write_byte(MPR121_NCLR, 0x0E); |  | ||||||
|   this->write_byte(MPR121_FDLR, 0x00); |  | ||||||
|  |  | ||||||
|   this->write_byte(MPR121_MHDF, 0x01); |     this->write_byte(MPR121_NHDT, 0x00); | ||||||
|   this->write_byte(MPR121_NHDF, 0x05); |     this->write_byte(MPR121_NCLT, 0x00); | ||||||
|   this->write_byte(MPR121_NCLF, 0x01); |     this->write_byte(MPR121_FDLT, 0x00); | ||||||
|   this->write_byte(MPR121_FDLF, 0x00); |  | ||||||
|  |  | ||||||
|   this->write_byte(MPR121_NHDT, 0x00); |     this->write_byte(MPR121_DEBOUNCE, 0); | ||||||
|   this->write_byte(MPR121_NCLT, 0x00); |     // default, 16uA charge current | ||||||
|   this->write_byte(MPR121_FDLT, 0x00); |     this->write_byte(MPR121_CONFIG1, 0x10); | ||||||
|  |     // 0.5uS encoding, 1ms period | ||||||
|  |     this->write_byte(MPR121_CONFIG2, 0x20); | ||||||
|  |  | ||||||
|   this->write_byte(MPR121_DEBOUNCE, 0); |     // Write the Electrode Configuration Register | ||||||
|   // default, 16uA charge current |     // * Highest 2 bits is "Calibration Lock", which we set to a value corresponding to 5 bits. | ||||||
|   this->write_byte(MPR121_CONFIG1, 0x10); |     // * The 2 bits below is "Proximity Enable" and are left at 0. | ||||||
|   // 0.5uS encoding, 1ms period |     // * The 4 least significant bits control how many electrodes are enabled. Electrodes are enabled | ||||||
|   this->write_byte(MPR121_CONFIG2, 0x20); |     //   as a range, starting at 0 up to the highest channel index used. | ||||||
|  |     this->write_byte(MPR121_ECR, 0x80 | (this->max_touch_channel_ + 1)); | ||||||
|  |  | ||||||
|   // Write the Electrode Configuration Register |     this->flush_gpio_(); | ||||||
|   // * Highest 2 bits is "Calibration Lock", which we set to a value corresponding to 5 bits. |     this->enable_loop(); | ||||||
|   // * The 2 bits below is "Proximity Enable" and are left at 0. |   }); | ||||||
|   // * The 4 least significant bits control how many electrodes are enabled. Electrodes are enabled |  | ||||||
|   //   as a range, starting at 0 up to the highest channel index used. |  | ||||||
|   this->write_byte(MPR121_ECR, 0x80 | (this->max_touch_channel_ + 1)); |  | ||||||
|  |  | ||||||
|   this->flush_gpio_(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void MPR121Component::set_touch_debounce(uint8_t debounce) { | void MPR121Component::set_touch_debounce(uint8_t debounce) { | ||||||
| @@ -73,9 +75,6 @@ void MPR121Component::dump_config() { | |||||||
|     case COMMUNICATION_FAILED: |     case COMMUNICATION_FAILED: | ||||||
|       ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); |       ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); | ||||||
|       break; |       break; | ||||||
|     case WRONG_CHIP_STATE: |  | ||||||
|       ESP_LOGE(TAG, "MPR121 has wrong default value for CONFIG2?"); |  | ||||||
|       break; |  | ||||||
|     case NONE: |     case NONE: | ||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
|   | |||||||
| @@ -88,7 +88,6 @@ class MPR121Component : public Component, public i2c::I2CDevice { | |||||||
|   enum ErrorCode { |   enum ErrorCode { | ||||||
|     NONE = 0, |     NONE = 0, | ||||||
|     COMMUNICATION_FAILED, |     COMMUNICATION_FAILED, | ||||||
|     WRONG_CHIP_STATE, |  | ||||||
|   } error_code_{NONE}; |   } error_code_{NONE}; | ||||||
|  |  | ||||||
|   bool flush_gpio_(); |   bool flush_gpio_(); | ||||||
|   | |||||||
| @@ -218,7 +218,7 @@ void NAU7802Sensor::dump_config() { | |||||||
|  |  | ||||||
| void NAU7802Sensor::write_value_(uint8_t start_reg, size_t size, int32_t value) { | void NAU7802Sensor::write_value_(uint8_t start_reg, size_t size, int32_t value) { | ||||||
|   uint8_t data[4]; |   uint8_t data[4]; | ||||||
|   for (int i = 0; i < size; i++) { |   for (size_t i = 0; i < size; i++) { | ||||||
|     data[i] = 0xFF & (value >> (size - 1 - i) * 8); |     data[i] = 0xFF & (value >> (size - 1 - i) * 8); | ||||||
|   } |   } | ||||||
|   this->write_register(start_reg, data, size); |   this->write_register(start_reg, data, size); | ||||||
| @@ -228,7 +228,7 @@ int32_t NAU7802Sensor::read_value_(uint8_t start_reg, size_t size) { | |||||||
|   uint8_t data[4]; |   uint8_t data[4]; | ||||||
|   this->read_register(start_reg, data, size); |   this->read_register(start_reg, data, size); | ||||||
|   int32_t result = 0; |   int32_t result = 0; | ||||||
|   for (int i = 0; i < size; i++) { |   for (size_t i = 0; i < size; i++) { | ||||||
|     result |= data[i] << (size - 1 - i) * 8; |     result |= data[i] << (size - 1 - i) * 8; | ||||||
|   } |   } | ||||||
|   // extend sign bit |   // extend sign bit | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ bool Nextion::check_connect_() { | |||||||
|   this->recv_ret_string_(response, 0, false); |   this->recv_ret_string_(response, 0, false); | ||||||
|   if (!response.empty() && response[0] == 0x1A) { |   if (!response.empty() && response[0] == 0x1A) { | ||||||
|     // Swallow invalid variable name responses that may be caused by the above commands |     // Swallow invalid variable name responses that may be caused by the above commands | ||||||
|     ESP_LOGD(TAG, "0x1A error ignored (setup)"); |     ESP_LOGV(TAG, "0x1A error ignored (setup)"); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|   if (response.empty() || response.find("comok") == std::string::npos) { |   if (response.empty() || response.find("comok") == std::string::npos) { | ||||||
| @@ -334,7 +334,7 @@ void Nextion::loop() { | |||||||
|       this->started_ms_ = App.get_loop_component_start_time(); |       this->started_ms_ = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|     if (this->started_ms_ + this->startup_override_ms_ < App.get_loop_component_start_time()) { |     if (this->started_ms_ + this->startup_override_ms_ < App.get_loop_component_start_time()) { | ||||||
|       ESP_LOGD(TAG, "Manual ready set"); |       ESP_LOGV(TAG, "Manual ready set"); | ||||||
|       this->connection_state_.nextion_reports_is_setup_ = true; |       this->connection_state_.nextion_reports_is_setup_ = true; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -544,7 +544,7 @@ void Nextion::process_nextion_commands_() { | |||||||
|         uint8_t page_id = to_process[0]; |         uint8_t page_id = to_process[0]; | ||||||
|         uint8_t component_id = to_process[1]; |         uint8_t component_id = to_process[1]; | ||||||
|         uint8_t touch_event = to_process[2];  // 0 -> release, 1 -> press |         uint8_t touch_event = to_process[2];  // 0 -> release, 1 -> press | ||||||
|         ESP_LOGD(TAG, "Touch %s: page %u comp %u", touch_event ? "PRESS" : "RELEASE", page_id, component_id); |         ESP_LOGV(TAG, "Touch %s: page %u comp %u", touch_event ? "PRESS" : "RELEASE", page_id, component_id); | ||||||
|         for (auto *touch : this->touch_) { |         for (auto *touch : this->touch_) { | ||||||
|           touch->process_touch(page_id, component_id, touch_event != 0); |           touch->process_touch(page_id, component_id, touch_event != 0); | ||||||
|         } |         } | ||||||
| @@ -559,7 +559,7 @@ void Nextion::process_nextion_commands_() { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         uint8_t page_id = to_process[0]; |         uint8_t page_id = to_process[0]; | ||||||
|         ESP_LOGD(TAG, "New page: %u", page_id); |         ESP_LOGV(TAG, "New page: %u", page_id); | ||||||
|         this->page_callback_.call(page_id); |         this->page_callback_.call(page_id); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
| @@ -577,7 +577,7 @@ void Nextion::process_nextion_commands_() { | |||||||
|         const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; |         const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; | ||||||
|         const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; |         const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; | ||||||
|         const uint8_t touch_event = to_process[4];  // 0 -> release, 1 -> press |         const uint8_t touch_event = to_process[4];  // 0 -> release, 1 -> press | ||||||
|         ESP_LOGD(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y); |         ESP_LOGV(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -676,7 +676,7 @@ void Nextion::process_nextion_commands_() { | |||||||
|       } |       } | ||||||
|       case 0x88:  // system successful start up |       case 0x88:  // system successful start up | ||||||
|       { |       { | ||||||
|         ESP_LOGD(TAG, "System start: %zu", to_process_length); |         ESP_LOGV(TAG, "System start: %zu", to_process_length); | ||||||
|         this->connection_state_.nextion_reports_is_setup_ = true; |         this->connection_state_.nextion_reports_is_setup_ = true; | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
| @@ -922,7 +922,7 @@ void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::s | |||||||
| } | } | ||||||
|  |  | ||||||
| void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { | void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { | ||||||
|   ESP_LOGD(TAG, "State: %s='%s'", name.c_str(), state.c_str()); |   ESP_LOGV(TAG, "State: %s='%s'", name.c_str(), state.c_str()); | ||||||
|  |  | ||||||
|   for (auto *sensor : this->textsensortype_) { |   for (auto *sensor : this->textsensortype_) { | ||||||
|     if (name == sensor->get_variable_name()) { |     if (name == sensor->get_variable_name()) { | ||||||
| @@ -933,7 +933,7 @@ void Nextion::set_nextion_text_state(const std::string &name, const std::string | |||||||
| } | } | ||||||
|  |  | ||||||
| void Nextion::all_components_send_state_(bool force_update) { | void Nextion::all_components_send_state_(bool force_update) { | ||||||
|   ESP_LOGD(TAG, "Send states"); |   ESP_LOGV(TAG, "Send states"); | ||||||
|   for (auto *binarysensortype : this->binarysensortype_) { |   for (auto *binarysensortype : this->binarysensortype_) { | ||||||
|     if (force_update || binarysensortype->get_needs_to_send_update()) |     if (force_update || binarysensortype->get_needs_to_send_update()) | ||||||
|       binarysensortype->send_state_to_nextion(); |       binarysensortype->send_state_to_nextion(); | ||||||
|   | |||||||
| @@ -7,6 +7,17 @@ namespace number { | |||||||
|  |  | ||||||
| static const char *const TAG = "number"; | static const char *const TAG = "number"; | ||||||
|  |  | ||||||
|  | // Helper functions to reduce code size for logging | ||||||
|  | void NumberCall::log_perform_warning_(const LogString *message) { | ||||||
|  |   ESP_LOGW(TAG, "'%s': %s", this->parent_->get_name().c_str(), LOG_STR_ARG(message)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void NumberCall::log_perform_warning_value_range_(const LogString *comparison, const LogString *limit_type, float val, | ||||||
|  |                                                   float limit) { | ||||||
|  |   ESP_LOGW(TAG, "'%s': %f %s %s %f", this->parent_->get_name().c_str(), val, LOG_STR_ARG(comparison), | ||||||
|  |            LOG_STR_ARG(limit_type), limit); | ||||||
|  | } | ||||||
|  |  | ||||||
| NumberCall &NumberCall::set_value(float value) { return this->with_operation(NUMBER_OP_SET).with_value(value); } | NumberCall &NumberCall::set_value(float value) { return this->with_operation(NUMBER_OP_SET).with_value(value); } | ||||||
|  |  | ||||||
| NumberCall &NumberCall::number_increment(bool cycle) { | NumberCall &NumberCall::number_increment(bool cycle) { | ||||||
| @@ -42,7 +53,7 @@ void NumberCall::perform() { | |||||||
|   const auto &traits = parent->traits; |   const auto &traits = parent->traits; | ||||||
|  |  | ||||||
|   if (this->operation_ == NUMBER_OP_NONE) { |   if (this->operation_ == NUMBER_OP_NONE) { | ||||||
|     ESP_LOGW(TAG, "'%s' - NumberCall performed without selecting an operation", name); |     this->log_perform_warning_(LOG_STR("No operation")); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -51,28 +62,28 @@ void NumberCall::perform() { | |||||||
|   float max_value = traits.get_max_value(); |   float max_value = traits.get_max_value(); | ||||||
|  |  | ||||||
|   if (this->operation_ == NUMBER_OP_SET) { |   if (this->operation_ == NUMBER_OP_SET) { | ||||||
|     ESP_LOGD(TAG, "'%s' - Setting number value", name); |     ESP_LOGD(TAG, "'%s': Setting value", name); | ||||||
|     if (!this->value_.has_value() || std::isnan(*this->value_)) { |     if (!this->value_.has_value() || std::isnan(*this->value_)) { | ||||||
|       ESP_LOGW(TAG, "'%s' - No value set for NumberCall", name); |       this->log_perform_warning_(LOG_STR("No value")); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     target_value = this->value_.value(); |     target_value = this->value_.value(); | ||||||
|   } else if (this->operation_ == NUMBER_OP_TO_MIN) { |   } else if (this->operation_ == NUMBER_OP_TO_MIN) { | ||||||
|     if (std::isnan(min_value)) { |     if (std::isnan(min_value)) { | ||||||
|       ESP_LOGW(TAG, "'%s' - Can't set to min value through NumberCall: no min_value defined", name); |       this->log_perform_warning_(LOG_STR("min undefined")); | ||||||
|     } else { |     } else { | ||||||
|       target_value = min_value; |       target_value = min_value; | ||||||
|     } |     } | ||||||
|   } else if (this->operation_ == NUMBER_OP_TO_MAX) { |   } else if (this->operation_ == NUMBER_OP_TO_MAX) { | ||||||
|     if (std::isnan(max_value)) { |     if (std::isnan(max_value)) { | ||||||
|       ESP_LOGW(TAG, "'%s' - Can't set to max value through NumberCall: no max_value defined", name); |       this->log_perform_warning_(LOG_STR("max undefined")); | ||||||
|     } else { |     } else { | ||||||
|       target_value = max_value; |       target_value = max_value; | ||||||
|     } |     } | ||||||
|   } else if (this->operation_ == NUMBER_OP_INCREMENT) { |   } else if (this->operation_ == NUMBER_OP_INCREMENT) { | ||||||
|     ESP_LOGD(TAG, "'%s' - Increment number, with%s cycling", name, this->cycle_ ? "" : "out"); |     ESP_LOGD(TAG, "'%s': Increment with%s cycling", name, this->cycle_ ? "" : "out"); | ||||||
|     if (!parent->has_state()) { |     if (!parent->has_state()) { | ||||||
|       ESP_LOGW(TAG, "'%s' - Can't increment number through NumberCall: no active state to modify", name); |       this->log_perform_warning_(LOG_STR("Can't increment, no state")); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     auto step = traits.get_step(); |     auto step = traits.get_step(); | ||||||
| @@ -85,9 +96,9 @@ void NumberCall::perform() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } else if (this->operation_ == NUMBER_OP_DECREMENT) { |   } else if (this->operation_ == NUMBER_OP_DECREMENT) { | ||||||
|     ESP_LOGD(TAG, "'%s' - Decrement number, with%s cycling", name, this->cycle_ ? "" : "out"); |     ESP_LOGD(TAG, "'%s': Decrement with%s cycling", name, this->cycle_ ? "" : "out"); | ||||||
|     if (!parent->has_state()) { |     if (!parent->has_state()) { | ||||||
|       ESP_LOGW(TAG, "'%s' - Can't decrement number through NumberCall: no active state to modify", name); |       this->log_perform_warning_(LOG_STR("Can't decrement, no state")); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     auto step = traits.get_step(); |     auto step = traits.get_step(); | ||||||
| @@ -102,15 +113,15 @@ void NumberCall::perform() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (target_value < min_value) { |   if (target_value < min_value) { | ||||||
|     ESP_LOGW(TAG, "'%s' - Value %f must not be less than minimum %f", name, target_value, min_value); |     this->log_perform_warning_value_range_(LOG_STR("<"), LOG_STR("min"), target_value, min_value); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (target_value > max_value) { |   if (target_value > max_value) { | ||||||
|     ESP_LOGW(TAG, "'%s' - Value %f must not be greater than maximum %f", name, target_value, max_value); |     this->log_perform_warning_value_range_(LOG_STR(">"), LOG_STR("max"), target_value, max_value); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "  New number value: %f", target_value); |   ESP_LOGD(TAG, "  New value: %f", target_value); | ||||||
|   this->parent_->control(target_value); |   this->parent_->control(target_value); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
| #include "number_traits.h" | #include "number_traits.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -33,6 +34,10 @@ class NumberCall { | |||||||
|   NumberCall &with_cycle(bool cycle); |   NumberCall &with_cycle(bool cycle); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   void log_perform_warning_(const LogString *message); | ||||||
|  |   void log_perform_warning_value_range_(const LogString *comparison, const LogString *limit_type, float val, | ||||||
|  |                                         float limit); | ||||||
|  |  | ||||||
|   Number *const parent_; |   Number *const parent_; | ||||||
|   NumberOperation operation_{NUMBER_OP_NONE}; |   NumberOperation operation_{NUMBER_OP_NONE}; | ||||||
|   optional<float> value_; |   optional<float> value_; | ||||||
|   | |||||||
| @@ -117,7 +117,8 @@ int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) { | |||||||
|         this->paint_index_++; |         this->paint_index_++; | ||||||
|         this->current_index_ += 3; |         this->current_index_ += 3; | ||||||
|         index += 3; |         index += 3; | ||||||
|         if (x == this->width_ - 1 && this->padding_bytes_ > 0) { |         size_t last_col = static_cast<size_t>(this->width_) - 1; | ||||||
|  |         if (x == last_col && this->padding_bytes_ > 0) { | ||||||
|           index += this->padding_bytes_; |           index += this->padding_bytes_; | ||||||
|           this->current_index_ += this->padding_bytes_; |           this->current_index_ += this->padding_bytes_; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -25,8 +25,10 @@ static int draw_callback(JPEGDRAW *jpeg) { | |||||||
|   // to avoid crashing. |   // to avoid crashing. | ||||||
|   App.feed_wdt(); |   App.feed_wdt(); | ||||||
|   size_t position = 0; |   size_t position = 0; | ||||||
|   for (size_t y = 0; y < jpeg->iHeight; y++) { |   size_t height = static_cast<size_t>(jpeg->iHeight); | ||||||
|     for (size_t x = 0; x < jpeg->iWidth; x++) { |   size_t width = static_cast<size_t>(jpeg->iWidth); | ||||||
|  |   for (size_t y = 0; y < height; y++) { | ||||||
|  |     for (size_t x = 0; x < width; x++) { | ||||||
|       auto rg = decode_value(jpeg->pPixels[position++]); |       auto rg = decode_value(jpeg->pPixels[position++]); | ||||||
|       auto ba = decode_value(jpeg->pPixels[position++]); |       auto ba = decode_value(jpeg->pPixels[position++]); | ||||||
|       Color color(rg[1], rg[0], ba[1], ba[0]); |       Color color(rg[1], rg[0], ba[1], ba[0]); | ||||||
|   | |||||||
| @@ -143,11 +143,10 @@ void OpenThreadSrpComponent::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this |   // Get mdns services and copy their data (strings are copied with strdup below) | ||||||
|   // component |   const auto &mdns_services = this->mdns_->get_services(); | ||||||
|   this->mdns_services_ = this->mdns_->get_services(); |   ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", mdns_services.size()); | ||||||
|   ESP_LOGD(TAG, "Setting up SRP services. count = %d\n", this->mdns_services_.size()); |   for (const auto &service : mdns_services) { | ||||||
|   for (const auto &service : this->mdns_services_) { |  | ||||||
|     otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); |     otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); | ||||||
|     if (!entry) { |     if (!entry) { | ||||||
|       ESP_LOGW(TAG, "Failed to allocate service entry"); |       ESP_LOGW(TAG, "Failed to allocate service entry"); | ||||||
|   | |||||||
| @@ -57,7 +57,6 @@ class OpenThreadSrpComponent : public Component { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   esphome::mdns::MDNSComponent *mdns_{nullptr}; |   esphome::mdns::MDNSComponent *mdns_{nullptr}; | ||||||
|   std::vector<esphome::mdns::MDNSService> mdns_services_; |  | ||||||
|   std::vector<std::unique_ptr<uint8_t[]>> memory_pool_; |   std::vector<std::unique_ptr<uint8_t[]>> memory_pool_; | ||||||
|   void *pool_alloc_(size_t size); |   void *pool_alloc_(size_t size); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ float PIDController::weighted_average_(std::deque<float> &list, float new_value, | |||||||
|   list.push_front(new_value); |   list.push_front(new_value); | ||||||
|  |  | ||||||
|   // keep only 'samples' readings, by popping off the back of the list |   // keep only 'samples' readings, by popping off the back of the list | ||||||
|   while (list.size() > samples) |   while (samples > 0 && list.size() > static_cast<size_t>(samples)) | ||||||
|     list.pop_back(); |     list.pop_back(); | ||||||
|  |  | ||||||
|   // calculate and return the average of all values in the list |   // calculate and return the average of all values in the list | ||||||
|   | |||||||
| @@ -110,21 +110,21 @@ std::string PrometheusHandler::relabel_name_(EntityBase *obj) { | |||||||
|  |  | ||||||
| void PrometheusHandler::add_area_label_(AsyncResponseStream *stream, std::string &area) { | void PrometheusHandler::add_area_label_(AsyncResponseStream *stream, std::string &area) { | ||||||
|   if (!area.empty()) { |   if (!area.empty()) { | ||||||
|     stream->print(F("\",area=\"")); |     stream->print(ESPHOME_F("\",area=\"")); | ||||||
|     stream->print(area.c_str()); |     stream->print(area.c_str()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::add_node_label_(AsyncResponseStream *stream, std::string &node) { | void PrometheusHandler::add_node_label_(AsyncResponseStream *stream, std::string &node) { | ||||||
|   if (!node.empty()) { |   if (!node.empty()) { | ||||||
|     stream->print(F("\",node=\"")); |     stream->print(ESPHOME_F("\",node=\"")); | ||||||
|     stream->print(node.c_str()); |     stream->print(node.c_str()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name) { | void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, std::string &friendly_name) { | ||||||
|   if (!friendly_name.empty()) { |   if (!friendly_name.empty()) { | ||||||
|     stream->print(F("\",friendly_name=\"")); |     stream->print(ESPHOME_F("\",friendly_name=\"")); | ||||||
|     stream->print(friendly_name.c_str()); |     stream->print(friendly_name.c_str()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -132,8 +132,8 @@ void PrometheusHandler::add_friendly_name_label_(AsyncResponseStream *stream, st | |||||||
| // Type-specific implementation | // Type-specific implementation | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { | void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_sensor_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_sensor_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_sensor_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_sensor_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj, std::string &area, | void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj, std::string &area, | ||||||
|                                     std::string &node, std::string &friendly_name) { |                                     std::string &node, std::string &friendly_name) { | ||||||
| @@ -141,37 +141,37 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor | |||||||
|     return; |     return; | ||||||
|   if (!std::isnan(obj->state)) { |   if (!std::isnan(obj->state)) { | ||||||
|     // We have a valid value, output this value |     // We have a valid value, output this value | ||||||
|     stream->print(F("esphome_sensor_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_sensor_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 0\n")); |     stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|     // Data itself |     // Data itself | ||||||
|     stream->print(F("esphome_sensor_value{id=\"")); |     stream->print(ESPHOME_F("esphome_sensor_value{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\",unit=\"")); |     stream->print(ESPHOME_F("\",unit=\"")); | ||||||
|     stream->print(obj->get_unit_of_measurement().c_str()); |     stream->print(obj->get_unit_of_measurement().c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str()); |     stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str()); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } else { |   } else { | ||||||
|     // Invalid state |     // Invalid state | ||||||
|     stream->print(F("esphome_sensor_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_sensor_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -179,8 +179,8 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor | |||||||
| // Type-specific implementation | // Type-specific implementation | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| void PrometheusHandler::binary_sensor_type_(AsyncResponseStream *stream) { | void PrometheusHandler::binary_sensor_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_binary_sensor_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_binary_sensor_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_binary_sensor_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_binary_sensor_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj, | void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj, | ||||||
|                                            std::string &area, std::string &node, std::string &friendly_name) { |                                            std::string &area, std::string &node, std::string &friendly_name) { | ||||||
| @@ -188,204 +188,204 @@ void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_s | |||||||
|     return; |     return; | ||||||
|   if (obj->has_state()) { |   if (obj->has_state()) { | ||||||
|     // We have a valid value, output this value |     // We have a valid value, output this value | ||||||
|     stream->print(F("esphome_binary_sensor_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_binary_sensor_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 0\n")); |     stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|     // Data itself |     // Data itself | ||||||
|     stream->print(F("esphome_binary_sensor_value{id=\"")); |     stream->print(ESPHOME_F("esphome_binary_sensor_value{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(obj->state); |     stream->print(obj->state); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } else { |   } else { | ||||||
|     // Invalid state |     // Invalid state | ||||||
|     stream->print(F("esphome_binary_sensor_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_binary_sensor_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
| void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { | void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_fan_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_fan_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_fan_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_fan_failed gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_fan_speed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_fan_speed gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_fan_oscillation gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_fan_oscillation gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj, std::string &area, std::string &node, | void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj, std::string &area, std::string &node, | ||||||
|                                  std::string &friendly_name) { |                                  std::string &friendly_name) { | ||||||
|   if (obj->is_internal() && !this->include_internal_) |   if (obj->is_internal() && !this->include_internal_) | ||||||
|     return; |     return; | ||||||
|   stream->print(F("esphome_fan_failed{id=\"")); |   stream->print(ESPHOME_F("esphome_fan_failed{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} 0\n")); |   stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|   // Data itself |   // Data itself | ||||||
|   stream->print(F("esphome_fan_value{id=\"")); |   stream->print(ESPHOME_F("esphome_fan_value{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(obj->state); |   stream->print(obj->state); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   // Speed if available |   // Speed if available | ||||||
|   if (obj->get_traits().supports_speed()) { |   if (obj->get_traits().supports_speed()) { | ||||||
|     stream->print(F("esphome_fan_speed{id=\"")); |     stream->print(ESPHOME_F("esphome_fan_speed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(obj->speed); |     stream->print(obj->speed); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } |   } | ||||||
|   // Oscillation if available |   // Oscillation if available | ||||||
|   if (obj->get_traits().supports_oscillation()) { |   if (obj->get_traits().supports_oscillation()) { | ||||||
|     stream->print(F("esphome_fan_oscillation{id=\"")); |     stream->print(ESPHOME_F("esphome_fan_oscillation{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(obj->oscillating); |     stream->print(obj->oscillating); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
| void PrometheusHandler::light_type_(AsyncResponseStream *stream) { | void PrometheusHandler::light_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_light_state gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_light_state gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_light_color gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_light_color gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_light_effect_active gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_light_effect_active gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj, std::string &area, | void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj, std::string &area, | ||||||
|                                    std::string &node, std::string &friendly_name) { |                                    std::string &node, std::string &friendly_name) { | ||||||
|   if (obj->is_internal() && !this->include_internal_) |   if (obj->is_internal() && !this->include_internal_) | ||||||
|     return; |     return; | ||||||
|   // State |   // State | ||||||
|   stream->print(F("esphome_light_state{id=\"")); |   stream->print(ESPHOME_F("esphome_light_state{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(obj->remote_values.is_on()); |   stream->print(obj->remote_values.is_on()); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   // Brightness and RGBW |   // Brightness and RGBW | ||||||
|   light::LightColorValues color = obj->current_values; |   light::LightColorValues color = obj->current_values; | ||||||
|   float brightness, r, g, b, w; |   float brightness, r, g, b, w; | ||||||
|   color.as_brightness(&brightness); |   color.as_brightness(&brightness); | ||||||
|   color.as_rgbw(&r, &g, &b, &w); |   color.as_rgbw(&r, &g, &b, &w); | ||||||
|   stream->print(F("esphome_light_color{id=\"")); |   stream->print(ESPHOME_F("esphome_light_color{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",channel=\"brightness\"} ")); |   stream->print(ESPHOME_F("\",channel=\"brightness\"} ")); | ||||||
|   stream->print(brightness); |   stream->print(brightness); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   stream->print(F("esphome_light_color{id=\"")); |   stream->print(ESPHOME_F("esphome_light_color{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",channel=\"r\"} ")); |   stream->print(ESPHOME_F("\",channel=\"r\"} ")); | ||||||
|   stream->print(r); |   stream->print(r); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   stream->print(F("esphome_light_color{id=\"")); |   stream->print(ESPHOME_F("esphome_light_color{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",channel=\"g\"} ")); |   stream->print(ESPHOME_F("\",channel=\"g\"} ")); | ||||||
|   stream->print(g); |   stream->print(g); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   stream->print(F("esphome_light_color{id=\"")); |   stream->print(ESPHOME_F("esphome_light_color{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",channel=\"b\"} ")); |   stream->print(ESPHOME_F("\",channel=\"b\"} ")); | ||||||
|   stream->print(b); |   stream->print(b); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   stream->print(F("esphome_light_color{id=\"")); |   stream->print(ESPHOME_F("esphome_light_color{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",channel=\"w\"} ")); |   stream->print(ESPHOME_F("\",channel=\"w\"} ")); | ||||||
|   stream->print(w); |   stream->print(w); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   // Effect |   // Effect | ||||||
|   std::string effect = obj->get_effect_name(); |   std::string effect = obj->get_effect_name(); | ||||||
|   if (effect == "None") { |   if (effect == "None") { | ||||||
|     stream->print(F("esphome_light_effect_active{id=\"")); |     stream->print(ESPHOME_F("esphome_light_effect_active{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\",effect=\"None\"} 0\n")); |     stream->print(ESPHOME_F("\",effect=\"None\"} 0\n")); | ||||||
|   } else { |   } else { | ||||||
|     stream->print(F("esphome_light_effect_active{id=\"")); |     stream->print(ESPHOME_F("esphome_light_effect_active{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\",effect=\"")); |     stream->print(ESPHOME_F("\",effect=\"")); | ||||||
|     stream->print(effect.c_str()); |     stream->print(effect.c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| void PrometheusHandler::cover_type_(AsyncResponseStream *stream) { | void PrometheusHandler::cover_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_cover_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_cover_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_cover_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_cover_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj, std::string &area, std::string &node, | void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj, std::string &area, std::string &node, | ||||||
|                                    std::string &friendly_name) { |                                    std::string &friendly_name) { | ||||||
| @@ -393,118 +393,118 @@ void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *ob | |||||||
|     return; |     return; | ||||||
|   if (!std::isnan(obj->position)) { |   if (!std::isnan(obj->position)) { | ||||||
|     // We have a valid value, output this value |     // We have a valid value, output this value | ||||||
|     stream->print(F("esphome_cover_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_cover_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 0\n")); |     stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|     // Data itself |     // Data itself | ||||||
|     stream->print(F("esphome_cover_value{id=\"")); |     stream->print(ESPHOME_F("esphome_cover_value{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(obj->position); |     stream->print(obj->position); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|     if (obj->get_traits().get_supports_tilt()) { |     if (obj->get_traits().get_supports_tilt()) { | ||||||
|       stream->print(F("esphome_cover_tilt{id=\"")); |       stream->print(ESPHOME_F("esphome_cover_tilt{id=\"")); | ||||||
|       stream->print(relabel_id_(obj).c_str()); |       stream->print(relabel_id_(obj).c_str()); | ||||||
|       add_area_label_(stream, area); |       add_area_label_(stream, area); | ||||||
|       add_node_label_(stream, node); |       add_node_label_(stream, node); | ||||||
|       add_friendly_name_label_(stream, friendly_name); |       add_friendly_name_label_(stream, friendly_name); | ||||||
|       stream->print(F("\",name=\"")); |       stream->print(ESPHOME_F("\",name=\"")); | ||||||
|       stream->print(relabel_name_(obj).c_str()); |       stream->print(relabel_name_(obj).c_str()); | ||||||
|       stream->print(F("\"} ")); |       stream->print(ESPHOME_F("\"} ")); | ||||||
|       stream->print(obj->tilt); |       stream->print(obj->tilt); | ||||||
|       stream->print(F("\n")); |       stream->print(ESPHOME_F("\n")); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     // Invalid state |     // Invalid state | ||||||
|     stream->print(F("esphome_cover_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_cover_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| void PrometheusHandler::switch_type_(AsyncResponseStream *stream) { | void PrometheusHandler::switch_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_switch_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_switch_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_switch_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_switch_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj, std::string &area, | void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj, std::string &area, | ||||||
|                                     std::string &node, std::string &friendly_name) { |                                     std::string &node, std::string &friendly_name) { | ||||||
|   if (obj->is_internal() && !this->include_internal_) |   if (obj->is_internal() && !this->include_internal_) | ||||||
|     return; |     return; | ||||||
|   stream->print(F("esphome_switch_failed{id=\"")); |   stream->print(ESPHOME_F("esphome_switch_failed{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} 0\n")); |   stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|   // Data itself |   // Data itself | ||||||
|   stream->print(F("esphome_switch_value{id=\"")); |   stream->print(ESPHOME_F("esphome_switch_value{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(obj->state); |   stream->print(obj->state); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_LOCK | #ifdef USE_LOCK | ||||||
| void PrometheusHandler::lock_type_(AsyncResponseStream *stream) { | void PrometheusHandler::lock_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_lock_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_lock_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_lock_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_lock_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj, std::string &area, std::string &node, | void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj, std::string &area, std::string &node, | ||||||
|                                   std::string &friendly_name) { |                                   std::string &friendly_name) { | ||||||
|   if (obj->is_internal() && !this->include_internal_) |   if (obj->is_internal() && !this->include_internal_) | ||||||
|     return; |     return; | ||||||
|   stream->print(F("esphome_lock_failed{id=\"")); |   stream->print(ESPHOME_F("esphome_lock_failed{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} 0\n")); |   stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|   // Data itself |   // Data itself | ||||||
|   stream->print(F("esphome_lock_value{id=\"")); |   stream->print(ESPHOME_F("esphome_lock_value{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(obj->state); |   stream->print(obj->state); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| // Type-specific implementation | // Type-specific implementation | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| void PrometheusHandler::text_sensor_type_(AsyncResponseStream *stream) { | void PrometheusHandler::text_sensor_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_text_sensor_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_text_sensor_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_text_sensor_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_text_sensor_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj, std::string &area, | void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_sensor::TextSensor *obj, std::string &area, | ||||||
|                                          std::string &node, std::string &friendly_name) { |                                          std::string &node, std::string &friendly_name) { | ||||||
| @@ -512,37 +512,37 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso | |||||||
|     return; |     return; | ||||||
|   if (obj->has_state()) { |   if (obj->has_state()) { | ||||||
|     // We have a valid value, output this value |     // We have a valid value, output this value | ||||||
|     stream->print(F("esphome_text_sensor_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_text_sensor_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 0\n")); |     stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|     // Data itself |     // Data itself | ||||||
|     stream->print(F("esphome_text_sensor_value{id=\"")); |     stream->print(ESPHOME_F("esphome_text_sensor_value{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\",value=\"")); |     stream->print(ESPHOME_F("\",value=\"")); | ||||||
|     stream->print(obj->state.c_str()); |     stream->print(obj->state.c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(F("1.0")); |     stream->print(ESPHOME_F("1.0")); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } else { |   } else { | ||||||
|     // Invalid state |     // Invalid state | ||||||
|     stream->print(F("esphome_text_sensor_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_text_sensor_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -550,8 +550,8 @@ void PrometheusHandler::text_sensor_row_(AsyncResponseStream *stream, text_senso | |||||||
| // Type-specific implementation | // Type-specific implementation | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| void PrometheusHandler::number_type_(AsyncResponseStream *stream) { | void PrometheusHandler::number_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_number_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_number_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_number_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_number_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, | void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number *obj, std::string &area, | ||||||
|                                     std::string &node, std::string &friendly_name) { |                                     std::string &node, std::string &friendly_name) { | ||||||
| @@ -559,43 +559,43 @@ void PrometheusHandler::number_row_(AsyncResponseStream *stream, number::Number | |||||||
|     return; |     return; | ||||||
|   if (!std::isnan(obj->state)) { |   if (!std::isnan(obj->state)) { | ||||||
|     // We have a valid value, output this value |     // We have a valid value, output this value | ||||||
|     stream->print(F("esphome_number_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_number_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 0\n")); |     stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|     // Data itself |     // Data itself | ||||||
|     stream->print(F("esphome_number_value{id=\"")); |     stream->print(ESPHOME_F("esphome_number_value{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(obj->state); |     stream->print(obj->state); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } else { |   } else { | ||||||
|     // Invalid state |     // Invalid state | ||||||
|     stream->print(F("esphome_number_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_number_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| void PrometheusHandler::select_type_(AsyncResponseStream *stream) { | void PrometheusHandler::select_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_select_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_select_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_select_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_select_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, | void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select *obj, std::string &area, | ||||||
|                                     std::string &node, std::string &friendly_name) { |                                     std::string &node, std::string &friendly_name) { | ||||||
| @@ -603,105 +603,105 @@ void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select | |||||||
|     return; |     return; | ||||||
|   if (obj->has_state()) { |   if (obj->has_state()) { | ||||||
|     // We have a valid value, output this value |     // We have a valid value, output this value | ||||||
|     stream->print(F("esphome_select_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_select_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 0\n")); |     stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|     // Data itself |     // Data itself | ||||||
|     stream->print(F("esphome_select_value{id=\"")); |     stream->print(ESPHOME_F("esphome_select_value{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\",value=\"")); |     stream->print(ESPHOME_F("\",value=\"")); | ||||||
|     stream->print(obj->state.c_str()); |     stream->print(obj->state.c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(F("1.0")); |     stream->print(ESPHOME_F("1.0")); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } else { |   } else { | ||||||
|     // Invalid state |     // Invalid state | ||||||
|     stream->print(F("esphome_select_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_select_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
| void PrometheusHandler::media_player_type_(AsyncResponseStream *stream) { | void PrometheusHandler::media_player_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_media_player_state_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_media_player_state_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_media_player_volume gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_media_player_volume gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_media_player_is_muted gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_media_player_is_muted gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_media_player_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_media_player_failed gauge\n")); | ||||||
| } | } | ||||||
| void PrometheusHandler::media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, | void PrometheusHandler::media_player_row_(AsyncResponseStream *stream, media_player::MediaPlayer *obj, | ||||||
|                                           std::string &area, std::string &node, std::string &friendly_name) { |                                           std::string &area, std::string &node, std::string &friendly_name) { | ||||||
|   if (obj->is_internal() && !this->include_internal_) |   if (obj->is_internal() && !this->include_internal_) | ||||||
|     return; |     return; | ||||||
|   stream->print(F("esphome_media_player_failed{id=\"")); |   stream->print(ESPHOME_F("esphome_media_player_failed{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} 0\n")); |   stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|   // Data itself |   // Data itself | ||||||
|   stream->print(F("esphome_media_player_state_value{id=\"")); |   stream->print(ESPHOME_F("esphome_media_player_state_value{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",value=\"")); |   stream->print(ESPHOME_F("\",value=\"")); | ||||||
|   stream->print(media_player::media_player_state_to_string(obj->state)); |   stream->print(media_player::media_player_state_to_string(obj->state)); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(F("1.0")); |   stream->print(ESPHOME_F("1.0")); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   stream->print(F("esphome_media_player_volume{id=\"")); |   stream->print(ESPHOME_F("esphome_media_player_volume{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(obj->volume); |   stream->print(obj->volume); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   stream->print(F("esphome_media_player_is_muted{id=\"")); |   stream->print(ESPHOME_F("esphome_media_player_is_muted{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   if (obj->is_muted()) { |   if (obj->is_muted()) { | ||||||
|     stream->print(F("1.0")); |     stream->print(ESPHOME_F("1.0")); | ||||||
|   } else { |   } else { | ||||||
|     stream->print(F("0.0")); |     stream->print(ESPHOME_F("0.0")); | ||||||
|   } |   } | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
| void PrometheusHandler::update_entity_type_(AsyncResponseStream *stream) { | void PrometheusHandler::update_entity_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_update_entity_state gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_update_entity_state gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_update_entity_info gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_update_entity_info gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_update_entity_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_update_entity_failed gauge\n")); | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::handle_update_state_(AsyncResponseStream *stream, update::UpdateState state) { | void PrometheusHandler::handle_update_state_(AsyncResponseStream *stream, update::UpdateState state) { | ||||||
| @@ -730,168 +730,168 @@ void PrometheusHandler::update_entity_row_(AsyncResponseStream *stream, update:: | |||||||
|     return; |     return; | ||||||
|   if (obj->has_state()) { |   if (obj->has_state()) { | ||||||
|     // We have a valid value, output this value |     // We have a valid value, output this value | ||||||
|     stream->print(F("esphome_update_entity_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_update_entity_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 0\n")); |     stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|     // First update state |     // First update state | ||||||
|     stream->print(F("esphome_update_entity_state{id=\"")); |     stream->print(ESPHOME_F("esphome_update_entity_state{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\",value=\"")); |     stream->print(ESPHOME_F("\",value=\"")); | ||||||
|     handle_update_state_(stream, obj->state); |     handle_update_state_(stream, obj->state); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(F("1.0")); |     stream->print(ESPHOME_F("1.0")); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|     // Next update info |     // Next update info | ||||||
|     stream->print(F("esphome_update_entity_info{id=\"")); |     stream->print(ESPHOME_F("esphome_update_entity_info{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\",current_version=\"")); |     stream->print(ESPHOME_F("\",current_version=\"")); | ||||||
|     stream->print(obj->update_info.current_version.c_str()); |     stream->print(obj->update_info.current_version.c_str()); | ||||||
|     stream->print(F("\",latest_version=\"")); |     stream->print(ESPHOME_F("\",latest_version=\"")); | ||||||
|     stream->print(obj->update_info.latest_version.c_str()); |     stream->print(obj->update_info.latest_version.c_str()); | ||||||
|     stream->print(F("\",title=\"")); |     stream->print(ESPHOME_F("\",title=\"")); | ||||||
|     stream->print(obj->update_info.title.c_str()); |     stream->print(obj->update_info.title.c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(F("1.0")); |     stream->print(ESPHOME_F("1.0")); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } else { |   } else { | ||||||
|     // Invalid state |     // Invalid state | ||||||
|     stream->print(F("esphome_update_entity_failed{id=\"")); |     stream->print(ESPHOME_F("esphome_update_entity_failed{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} 1\n")); |     stream->print(ESPHOME_F("\"} 1\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_VALVE | #ifdef USE_VALVE | ||||||
| void PrometheusHandler::valve_type_(AsyncResponseStream *stream) { | void PrometheusHandler::valve_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_valve_operation gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_valve_operation gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_valve_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_valve_failed gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_valve_position gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_valve_position gauge\n")); | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *obj, std::string &area, std::string &node, | void PrometheusHandler::valve_row_(AsyncResponseStream *stream, valve::Valve *obj, std::string &area, std::string &node, | ||||||
|                                    std::string &friendly_name) { |                                    std::string &friendly_name) { | ||||||
|   if (obj->is_internal() && !this->include_internal_) |   if (obj->is_internal() && !this->include_internal_) | ||||||
|     return; |     return; | ||||||
|   stream->print(F("esphome_valve_failed{id=\"")); |   stream->print(ESPHOME_F("esphome_valve_failed{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\"} 0\n")); |   stream->print(ESPHOME_F("\"} 0\n")); | ||||||
|   // Data itself |   // Data itself | ||||||
|   stream->print(F("esphome_valve_operation{id=\"")); |   stream->print(ESPHOME_F("esphome_valve_operation{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",operation=\"")); |   stream->print(ESPHOME_F("\",operation=\"")); | ||||||
|   stream->print(valve::valve_operation_to_str(obj->current_operation)); |   stream->print(valve::valve_operation_to_str(obj->current_operation)); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(F("1.0")); |   stream->print(ESPHOME_F("1.0")); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
|   // Now see if position is supported |   // Now see if position is supported | ||||||
|   if (obj->get_traits().get_supports_position()) { |   if (obj->get_traits().get_supports_position()) { | ||||||
|     stream->print(F("esphome_valve_position{id=\"")); |     stream->print(ESPHOME_F("esphome_valve_position{id=\"")); | ||||||
|     stream->print(relabel_id_(obj).c_str()); |     stream->print(relabel_id_(obj).c_str()); | ||||||
|     add_area_label_(stream, area); |     add_area_label_(stream, area); | ||||||
|     add_node_label_(stream, node); |     add_node_label_(stream, node); | ||||||
|     add_friendly_name_label_(stream, friendly_name); |     add_friendly_name_label_(stream, friendly_name); | ||||||
|     stream->print(F("\",name=\"")); |     stream->print(ESPHOME_F("\",name=\"")); | ||||||
|     stream->print(relabel_name_(obj).c_str()); |     stream->print(relabel_name_(obj).c_str()); | ||||||
|     stream->print(F("\"} ")); |     stream->print(ESPHOME_F("\"} ")); | ||||||
|     stream->print(obj->position); |     stream->print(obj->position); | ||||||
|     stream->print(F("\n")); |     stream->print(ESPHOME_F("\n")); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
| void PrometheusHandler::climate_type_(AsyncResponseStream *stream) { | void PrometheusHandler::climate_type_(AsyncResponseStream *stream) { | ||||||
|   stream->print(F("#TYPE esphome_climate_setting gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_climate_setting gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_climate_value gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_climate_value gauge\n")); | ||||||
|   stream->print(F("#TYPE esphome_climate_failed gauge\n")); |   stream->print(ESPHOME_F("#TYPE esphome_climate_failed gauge\n")); | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | ||||||
|                                              std::string &node, std::string &friendly_name, std::string &setting, |                                              std::string &node, std::string &friendly_name, std::string &setting, | ||||||
|                                              const LogString *setting_value) { |                                              const LogString *setting_value) { | ||||||
|   stream->print(F("esphome_climate_setting{id=\"")); |   stream->print(ESPHOME_F("esphome_climate_setting{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",category=\"")); |   stream->print(ESPHOME_F("\",category=\"")); | ||||||
|   stream->print(setting.c_str()); |   stream->print(setting.c_str()); | ||||||
|   stream->print(F("\",setting_value=\"")); |   stream->print(ESPHOME_F("\",setting_value=\"")); | ||||||
|   stream->print(LOG_STR_ARG(setting_value)); |   stream->print(LOG_STR_ARG(setting_value)); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(F("1.0")); |   stream->print(ESPHOME_F("1.0")); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | ||||||
|                                            std::string &node, std::string &friendly_name, std::string &category, |                                            std::string &node, std::string &friendly_name, std::string &category, | ||||||
|                                            std::string &climate_value) { |                                            std::string &climate_value) { | ||||||
|   stream->print(F("esphome_climate_value{id=\"")); |   stream->print(ESPHOME_F("esphome_climate_value{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",category=\"")); |   stream->print(ESPHOME_F("\",category=\"")); | ||||||
|   stream->print(category.c_str()); |   stream->print(category.c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   stream->print(climate_value.c_str()); |   stream->print(climate_value.c_str()); | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::climate_failed_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | void PrometheusHandler::climate_failed_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | ||||||
|                                             std::string &node, std::string &friendly_name, std::string &category, |                                             std::string &node, std::string &friendly_name, std::string &category, | ||||||
|                                             bool is_failed_value) { |                                             bool is_failed_value) { | ||||||
|   stream->print(F("esphome_climate_failed{id=\"")); |   stream->print(ESPHOME_F("esphome_climate_failed{id=\"")); | ||||||
|   stream->print(relabel_id_(obj).c_str()); |   stream->print(relabel_id_(obj).c_str()); | ||||||
|   add_area_label_(stream, area); |   add_area_label_(stream, area); | ||||||
|   add_node_label_(stream, node); |   add_node_label_(stream, node); | ||||||
|   add_friendly_name_label_(stream, friendly_name); |   add_friendly_name_label_(stream, friendly_name); | ||||||
|   stream->print(F("\",name=\"")); |   stream->print(ESPHOME_F("\",name=\"")); | ||||||
|   stream->print(relabel_name_(obj).c_str()); |   stream->print(relabel_name_(obj).c_str()); | ||||||
|   stream->print(F("\",category=\"")); |   stream->print(ESPHOME_F("\",category=\"")); | ||||||
|   stream->print(category.c_str()); |   stream->print(category.c_str()); | ||||||
|   stream->print(F("\"} ")); |   stream->print(ESPHOME_F("\"} ")); | ||||||
|   if (is_failed_value) { |   if (is_failed_value) { | ||||||
|     stream->print(F("1.0")); |     stream->print(ESPHOME_F("1.0")); | ||||||
|   } else { |   } else { | ||||||
|     stream->print(F("0.0")); |     stream->print(ESPHOME_F("0.0")); | ||||||
|   } |   } | ||||||
|   stream->print(F("\n")); |   stream->print(ESPHOME_F("\n")); | ||||||
| } | } | ||||||
|  |  | ||||||
| void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, | ||||||
|   | |||||||
| @@ -62,6 +62,11 @@ SPIRAM_SPEEDS = { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def supported() -> bool: | ||||||
|  |     variant = get_esp32_variant() | ||||||
|  |     return variant in SPIRAM_MODES | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_psram_mode(config): | def validate_psram_mode(config): | ||||||
|     esp32_config = fv.full_config.get()[PLATFORM_ESP32] |     esp32_config = fv.full_config.get()[PLATFORM_ESP32] | ||||||
|     if config[CONF_SPEED] == "120MHZ": |     if config[CONF_SPEED] == "120MHZ": | ||||||
| @@ -95,7 +100,7 @@ def get_config_schema(config): | |||||||
|     variant = get_esp32_variant() |     variant = get_esp32_variant() | ||||||
|     speeds = [f"{s}MHZ" for s in SPIRAM_SPEEDS.get(variant, [])] |     speeds = [f"{s}MHZ" for s in SPIRAM_SPEEDS.get(variant, [])] | ||||||
|     if not speeds: |     if not speeds: | ||||||
|         return cv.Invalid("PSRAM is not supported on this chip") |         raise cv.Invalid("PSRAM is not supported on this chip") | ||||||
|     modes = SPIRAM_MODES[variant] |     modes = SPIRAM_MODES[variant] | ||||||
|     return cv.Schema( |     return cv.Schema( | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ namespace esphome { | |||||||
| namespace qmc5883l { | namespace qmc5883l { | ||||||
|  |  | ||||||
| static const char *const TAG = "qmc5883l"; | static const char *const TAG = "qmc5883l"; | ||||||
|  |  | ||||||
| static const uint8_t QMC5883L_ADDRESS = 0x0D; | static const uint8_t QMC5883L_ADDRESS = 0x0D; | ||||||
|  |  | ||||||
| static const uint8_t QMC5883L_REGISTER_DATA_X_LSB = 0x00; | static const uint8_t QMC5883L_REGISTER_DATA_X_LSB = 0x00; | ||||||
| @@ -32,6 +33,10 @@ void QMC5883LComponent::setup() { | |||||||
|   } |   } | ||||||
|   delay(10); |   delay(10); | ||||||
|  |  | ||||||
|  |   if (this->drdy_pin_) { | ||||||
|  |     this->drdy_pin_->setup(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   uint8_t control_1 = 0; |   uint8_t control_1 = 0; | ||||||
|   control_1 |= 0b01 << 0;  // MODE (Mode) -> 0b00=standby, 0b01=continuous |   control_1 |= 0b01 << 0;  // MODE (Mode) -> 0b00=standby, 0b01=continuous | ||||||
|   control_1 |= this->datarate_ << 2; |   control_1 |= this->datarate_ << 2; | ||||||
| @@ -64,6 +69,7 @@ void QMC5883LComponent::setup() { | |||||||
|     high_freq_.start(); |     high_freq_.start(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void QMC5883LComponent::dump_config() { | void QMC5883LComponent::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "QMC5883L:"); |   ESP_LOGCONFIG(TAG, "QMC5883L:"); | ||||||
|   LOG_I2C_DEVICE(this); |   LOG_I2C_DEVICE(this); | ||||||
| @@ -77,11 +83,20 @@ void QMC5883LComponent::dump_config() { | |||||||
|   LOG_SENSOR("  ", "Z Axis", this->z_sensor_); |   LOG_SENSOR("  ", "Z Axis", this->z_sensor_); | ||||||
|   LOG_SENSOR("  ", "Heading", this->heading_sensor_); |   LOG_SENSOR("  ", "Heading", this->heading_sensor_); | ||||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   LOG_PIN("  DRDY Pin: ", this->drdy_pin_); | ||||||
| } | } | ||||||
|  |  | ||||||
| float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } | float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
| void QMC5883LComponent::update() { | void QMC5883LComponent::update() { | ||||||
|   i2c::ErrorCode err; |   i2c::ErrorCode err; | ||||||
|   uint8_t status = false; |   uint8_t status = false; | ||||||
|  |  | ||||||
|  |   // If DRDY pin is configured and the data is not ready return. | ||||||
|  |   if (this->drdy_pin_ && !this->drdy_pin_->digital_read()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Status byte gets cleared when data is read, so we have to read this first. |   // Status byte gets cleared when data is read, so we have to read this first. | ||||||
|   // If status and two axes are desired, it's possible to save one byte of traffic by enabling |   // If status and two axes are desired, it's possible to save one byte of traffic by enabling | ||||||
|   // ROL_PNT in setup and reading 7 bytes starting at the status register. |   // ROL_PNT in setup and reading 7 bytes starting at the status register. | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user