mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 08:31:47 +00:00 
			
		
		
		
	Compare commits
	
		
			119 Commits
		
	
	
		
			2025.8.0b4
			...
			ble_tx_pow
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					635cb08e63 | ||
| 
						 | 
					a92a08c2de | ||
| 
						 | 
					75595b08be | ||
| 
						 | 
					3c7aba0681 | ||
| 
						 | 
					e5d1c30797 | ||
| 
						 | 
					c171d13c8c | ||
| 
						 | 
					65d63de9b6 | ||
| 
						 | 
					9e712e4127 | ||
| 
						 | 
					9007621fd7 | ||
| 
						 | 
					c01a26607e | ||
| 
						 | 
					f6ca70970f | ||
| 
						 | 
					4dc11f05a7 | ||
| 
						 | 
					5e508f7461 | ||
| 
						 | 
					2aceb56606 | ||
| 
						 | 
					d071a074ef | ||
| 
						 | 
					7a459c8c20 | ||
| 
						 | 
					aebd21958a | ||
| 
						 | 
					c542db8bfe | ||
| 
						 | 
					d9dcfe66ec | ||
| 
						 | 
					8517c2e903 | ||
| 
						 | 
					684384892a | ||
| 
						 | 
					d560831d79 | ||
| 
						 | 
					fcc3c8e1b6 | ||
| 
						 | 
					959ffde60e | ||
| 
						 | 
					07715dd50f | ||
| 
						 | 
					03836ee2d2 | ||
| 
						 | 
					50408d9abb | ||
| 
						 | 
					0de7259428 | ||
| 
						 | 
					d054709c2d | ||
| 
						 | 
					da16887915 | ||
| 
						 | 
					6da8ec8d55 | ||
| 
						 | 
					d2752b38c9 | ||
| 
						 | 
					6004367ee2 | ||
| 
						 | 
					ecfeb8e4d3 | ||
| 
						 | 
					456c31262d | ||
| 
						 | 
					9f02575287 | ||
| 
						 | 
					07bca6103f | ||
| 
						 | 
					a58c3950bc | ||
| 
						 | 
					8fe582309e | ||
| 
						 | 
					b41a61c76e | ||
| 
						 | 
					61a5023888 | ||
| 
						 | 
					4396bc0d1a | ||
| 
						 | 
					acfce581fa | ||
| 
						 | 
					88303f39fa | ||
| 
						 | 
					ca19959d7c | ||
| 
						 | 
					9737b35579 | ||
| 
						 | 
					be9c20c357 | ||
| 
						 | 
					12ba4b142e | ||
| 
						 | 
					c096c6934d | ||
| 
						 | 
					17f787fc36 | ||
| 
						 | 
					5cd9a86dcb | ||
| 
						 | 
					83fe4b4ff3 | ||
| 
						 | 
					94accd5abe | ||
| 
						 | 
					3ca0015284 | ||
| 
						 | 
					33eddb6035 | ||
| 
						 | 
					72c58ae36d | ||
| 
						 | 
					35411d199f | ||
| 
						 | 
					d45944a9e2 | ||
| 
						 | 
					86f306ba9e | ||
| 
						 | 
					1b3b2f6e6f | ||
| 
						 | 
					2adb993242 | ||
| 
						 | 
					3ff5b4773b | ||
| 
						 | 
					2cbf4f30f9 | ||
| 
						 | 
					56b6dd31f1 | ||
| 
						 | 
					fc1b49e87d | ||
| 
						 | 
					0089619518 | ||
| 
						 | 
					5a6db28f1d | ||
| 
						 | 
					6819bbd8f8 | ||
| 
						 | 
					634f687c3e | ||
| 
						 | 
					e2a9b85924 | ||
| 
						 | 
					4ccc6aee09 | ||
| 
						 | 
					0eab908b0e | ||
| 
						 | 
					3964f9794b | ||
| 
						 | 
					a45137434b | ||
| 
						 | 
					9b1ebdb6da | ||
| 
						 | 
					5a1533bea9 | ||
| 
						 | 
					0b50ef227b | ||
| 
						 | 
					0e31bc1a67 | ||
| 
						 | 
					8e67df8059 | ||
| 
						 | 
					e1a0949ddb | ||
| 
						 | 
					a8775ba60b | ||
| 
						 | 
					2aaf951357 | ||
| 
						 | 
					82718e62e7 | ||
| 
						 | 
					4dab9c4400 | ||
| 
						 | 
					7e23d865e6 | ||
| 
						 | 
					8f118232e4 | ||
| 
						 | 
					3a6a66537c | ||
| 
						 | 
					7118bea031 | ||
| 
						 | 
					44bd8e5b54 | ||
| 
						 | 
					efaeb91803 | ||
| 
						 | 
					761c6c6685 | ||
| 
						 | 
					1f55486896 | ||
| 
						 | 
					6818439109 | ||
| 
						 | 
					0a77423073 | ||
| 
						 | 
					c29f8d0187 | ||
| 
						 | 
					2a3f80a82c | ||
| 
						 | 
					75f3adcd95 | ||
| 
						 | 
					daf8ec36ab | ||
| 
						 | 
					6c5632a0b3 | ||
| 
						 | 
					abecc0e8d8 | ||
| 
						 | 
					af9ecf3429 | ||
| 
						 | 
					5fa84439c2 | ||
| 
						 | 
					5d18afcd99 | ||
| 
						 | 
					117cffd2b0 | ||
| 
						 | 
					8ea1a3ed64 | ||
| 
						 | 
					4f29b3c7aa | ||
| 
						 | 
					3325592d67 | ||
| 
						 | 
					882237120e | ||
| 
						 | 
					71efaf097b | ||
| 
						 | 
					bd60dbb746 | ||
| 
						 | 
					6b5e43ca72 | ||
| 
						 | 
					46d433775b | ||
| 
						 | 
					7c4a54de90 | ||
| 
						 | 
					c3f1596498 | ||
| 
						 | 
					0d1949a61b | ||
| 
						 | 
					6a8722f33e | ||
| 
						 | 
					fff66072d4 | ||
| 
						 | 
					0dda3faed5 | ||
| 
						 | 
					40c0c36179 | 
@@ -9,7 +9,7 @@ This document provides essential context for AI models interacting with this pro
 | 
			
		||||
 | 
			
		||||
## 2. Core Technologies & Stack
 | 
			
		||||
 | 
			
		||||
*   **Languages:** Python (>=3.10), C++ (gnu++20)
 | 
			
		||||
*   **Languages:** Python (>=3.11), C++ (gnu++20)
 | 
			
		||||
*   **Frameworks & Runtimes:** PlatformIO, Arduino, ESP-IDF.
 | 
			
		||||
*   **Build Systems:** PlatformIO is the primary build system. CMake is used as an alternative.
 | 
			
		||||
*   **Configuration:** YAML.
 | 
			
		||||
@@ -38,7 +38,7 @@ This document provides essential context for AI models interacting with this pro
 | 
			
		||||
    5.  **Dashboard** (`esphome/dashboard/`): A web-based interface for device configuration, management, and OTA updates.
 | 
			
		||||
 | 
			
		||||
*   **Platform Support:**
 | 
			
		||||
    1.  **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (S2, S3, C3, etc.) and both IDF and Arduino frameworks.
 | 
			
		||||
    1.  **ESP32** (`components/esp32/`): Espressif ESP32 family. Supports multiple variants (Original, C2, C3, C5, C6, H2, P4, S2, S3) with ESP-IDF framework. Arduino framework supports only a subset of the variants (Original, C3, S2, S3).
 | 
			
		||||
    2.  **ESP8266** (`components/esp8266/`): Espressif ESP8266. Arduino framework only, with memory constraints.
 | 
			
		||||
    3.  **RP2040** (`components/rp2040/`): Raspberry Pi Pico/RP2040. Arduino framework with PIO (Programmable I/O) support.
 | 
			
		||||
    4.  **LibreTiny** (`components/libretiny/`): Realtek and Beken chips. Supports multiple chip families and auto-generated components.
 | 
			
		||||
@@ -60,7 +60,7 @@ This document provides essential context for AI models interacting with this pro
 | 
			
		||||
        ├── __init__.py          # Component configuration schema and code generation
 | 
			
		||||
        ├── [component].h        # C++ header file (if needed)
 | 
			
		||||
        ├── [component].cpp      # C++ implementation (if needed)
 | 
			
		||||
        └── [platform]/         # Platform-specific implementations
 | 
			
		||||
        └── [platform]/          # Platform-specific implementations
 | 
			
		||||
            ├── __init__.py      # Platform-specific configuration
 | 
			
		||||
            ├── [platform].h     # Platform C++ header
 | 
			
		||||
            └── [platform].cpp   # Platform C++ implementation
 | 
			
		||||
@@ -150,7 +150,8 @@ This document provides essential context for AI models interacting with this pro
 | 
			
		||||
*   **Configuration Validation:**
 | 
			
		||||
    *   **Common Validators:** `cv.int_`, `cv.float_`, `cv.string`, `cv.boolean`, `cv.int_range(min=0, max=100)`, `cv.positive_int`, `cv.percentage`.
 | 
			
		||||
    *   **Complex Validation:** `cv.All(cv.string, cv.Length(min=1, max=50))`, `cv.Any(cv.int_, cv.string)`.
 | 
			
		||||
    *   **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `cv.only_with_arduino`.
 | 
			
		||||
    *   **Platform-Specific:** `cv.only_on(["esp32", "esp8266"])`, `esp32.only_on_variant(...)`, `cv.only_on_esp32`, `cv.only_on_esp8266`, `cv.only_on_rp2040`.
 | 
			
		||||
    *   **Framework-Specific:** `cv.only_with_framework(...)`, `cv.only_with_arduino`, `cv.only_with_esp_idf`.
 | 
			
		||||
    *   **Schema Extensions:**
 | 
			
		||||
        ```python
 | 
			
		||||
        CONFIG_SCHEMA = cv.Schema({ ... })
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
6af8b429b94191fe8e239fcb3b73f7982d0266cb5b05ffbc81edaeac1bc8c273
 | 
			
		||||
4368db58e8f884aff245996b1e8b644cc0796c0bb2fa706d5740d40b823d3ac9
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								.github/workflows/auto-label-pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/auto-label-pr.yml
									
									
									
									
										vendored
									
									
								
							@@ -105,7 +105,9 @@ jobs:
 | 
			
		||||
 | 
			
		||||
            // Calculate data from PR files
 | 
			
		||||
            const changedFiles = prFiles.map(file => file.filename);
 | 
			
		||||
            const totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
 | 
			
		||||
            const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
 | 
			
		||||
            const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
 | 
			
		||||
            const totalChanges = totalAdditions + totalDeletions;
 | 
			
		||||
 | 
			
		||||
            console.log('Current labels:', currentLabels.join(', '));
 | 
			
		||||
            console.log('Changed files:', changedFiles.length);
 | 
			
		||||
@@ -231,16 +233,21 @@ jobs:
 | 
			
		||||
            // Strategy: PR size detection
 | 
			
		||||
            async function detectPRSize() {
 | 
			
		||||
              const labels = new Set();
 | 
			
		||||
              const testChanges = prFiles
 | 
			
		||||
                .filter(file => file.filename.startsWith('tests/'))
 | 
			
		||||
                .reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
 | 
			
		||||
 | 
			
		||||
              const nonTestChanges = totalChanges - testChanges;
 | 
			
		||||
 | 
			
		||||
              if (totalChanges <= SMALL_PR_THRESHOLD) {
 | 
			
		||||
                labels.add('small-pr');
 | 
			
		||||
                return labels;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              const testAdditions = prFiles
 | 
			
		||||
                .filter(file => file.filename.startsWith('tests/'))
 | 
			
		||||
                .reduce((sum, file) => sum + (file.additions || 0), 0);
 | 
			
		||||
              const testDeletions = prFiles
 | 
			
		||||
                .filter(file => file.filename.startsWith('tests/'))
 | 
			
		||||
                .reduce((sum, file) => sum + (file.deletions || 0), 0);
 | 
			
		||||
 | 
			
		||||
              const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
 | 
			
		||||
 | 
			
		||||
              // Don't add too-big if mega-pr label is already present
 | 
			
		||||
              if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
 | 
			
		||||
                labels.add('too-big');
 | 
			
		||||
@@ -375,7 +382,7 @@ jobs:
 | 
			
		||||
              const labels = new Set();
 | 
			
		||||
 | 
			
		||||
              // Check for missing tests
 | 
			
		||||
              if ((allLabels.has('new-component') || allLabels.has('new-platform')) && !allLabels.has('has-tests')) {
 | 
			
		||||
              if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
 | 
			
		||||
                labels.add('needs-tests');
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
@@ -412,10 +419,13 @@ jobs:
 | 
			
		||||
 | 
			
		||||
              // Too big message
 | 
			
		||||
              if (finalLabels.includes('too-big')) {
 | 
			
		||||
                const testChanges = prFiles
 | 
			
		||||
                const testAdditions = prFiles
 | 
			
		||||
                  .filter(file => file.filename.startsWith('tests/'))
 | 
			
		||||
                  .reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
 | 
			
		||||
                const nonTestChanges = totalChanges - testChanges;
 | 
			
		||||
                  .reduce((sum, file) => sum + (file.additions || 0), 0);
 | 
			
		||||
                const testDeletions = prFiles
 | 
			
		||||
                  .filter(file => file.filename.startsWith('tests/'))
 | 
			
		||||
                  .reduce((sum, file) => sum + (file.deletions || 0), 0);
 | 
			
		||||
                const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
 | 
			
		||||
 | 
			
		||||
                const tooManyLabels = finalLabels.length > MAX_LABELS;
 | 
			
		||||
                const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -156,7 +156,7 @@ jobs:
 | 
			
		||||
          . venv/bin/activate
 | 
			
		||||
          pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
 | 
			
		||||
      - name: Upload coverage to Codecov
 | 
			
		||||
        uses: codecov/codecov-action@v5.4.3
 | 
			
		||||
        uses: codecov/codecov-action@v5.5.0
 | 
			
		||||
        with:
 | 
			
		||||
          token: ${{ secrets.CODECOV_TOKEN }}
 | 
			
		||||
      - name: Save Python virtual environment cache
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								.github/workflows/needs-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/needs-docs.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,24 +0,0 @@
 | 
			
		||||
name: Needs Docs
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    types: [labeled, unlabeled]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check:
 | 
			
		||||
    name: Check
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check for needs-docs label
 | 
			
		||||
        uses: actions/github-script@v7.0.1
 | 
			
		||||
        with:
 | 
			
		||||
          script: |
 | 
			
		||||
            const { data: labels } = await github.rest.issues.listLabelsOnIssue({
 | 
			
		||||
              owner: context.repo.owner,
 | 
			
		||||
              repo: context.repo.repo,
 | 
			
		||||
              issue_number: context.issue.number
 | 
			
		||||
            });
 | 
			
		||||
            const needsDocs = labels.find(label => label.name === 'needs-docs');
 | 
			
		||||
            if (needsDocs) {
 | 
			
		||||
              core.setFailed('Pull request needs docs');
 | 
			
		||||
            }
 | 
			
		||||
							
								
								
									
										30
									
								
								.github/workflows/status-check-labels.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.github/workflows/status-check-labels.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
name: Status check labels
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    types: [labeled, unlabeled]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check:
 | 
			
		||||
    name: Check ${{ matrix.label }}
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        label:
 | 
			
		||||
          - needs-docs
 | 
			
		||||
          - merge-after-release
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check for ${{ matrix.label }} label
 | 
			
		||||
        uses: actions/github-script@v7.0.1
 | 
			
		||||
        with:
 | 
			
		||||
          script: |
 | 
			
		||||
            const { data: labels } = await github.rest.issues.listLabelsOnIssue({
 | 
			
		||||
              owner: context.repo.owner,
 | 
			
		||||
              repo: context.repo.repo,
 | 
			
		||||
              issue_number: context.issue.number
 | 
			
		||||
            });
 | 
			
		||||
            const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
 | 
			
		||||
            if (hasLabel) {
 | 
			
		||||
              core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
 | 
			
		||||
            }
 | 
			
		||||
@@ -11,7 +11,7 @@ ci:
 | 
			
		||||
repos:
 | 
			
		||||
  - repo: https://github.com/astral-sh/ruff-pre-commit
 | 
			
		||||
    # Ruff version.
 | 
			
		||||
    rev: v0.12.8
 | 
			
		||||
    rev: v0.12.10
 | 
			
		||||
    hooks:
 | 
			
		||||
      # Run the linter.
 | 
			
		||||
      - id: ruff
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								Doxyfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Doxyfile
									
									
									
									
									
								
							@@ -48,7 +48,7 @@ PROJECT_NAME           = ESPHome
 | 
			
		||||
# could be handy for archiving the generated documentation or if some version
 | 
			
		||||
# control system is used.
 | 
			
		||||
 | 
			
		||||
PROJECT_NUMBER         = 2025.8.0b4
 | 
			
		||||
PROJECT_NUMBER         = 2025.9.0-dev
 | 
			
		||||
 | 
			
		||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
 | 
			
		||||
# for a project that appears at the top of each page and should give viewer a
 | 
			
		||||
 
 | 
			
		||||
@@ -132,14 +132,17 @@ def choose_upload_log_host(
 | 
			
		||||
                ]
 | 
			
		||||
                resolved.append(choose_prompt(options, purpose=purpose))
 | 
			
		||||
            elif device == "OTA":
 | 
			
		||||
                if (show_ota and "ota" in CORE.config) or (
 | 
			
		||||
                    show_api and "api" in CORE.config
 | 
			
		||||
                if CORE.address and (
 | 
			
		||||
                    (show_ota and "ota" in CORE.config)
 | 
			
		||||
                    or (show_api and "api" in CORE.config)
 | 
			
		||||
                ):
 | 
			
		||||
                    resolved.append(CORE.address)
 | 
			
		||||
                elif show_mqtt and has_mqtt_logging():
 | 
			
		||||
                    resolved.append("MQTT")
 | 
			
		||||
            else:
 | 
			
		||||
                resolved.append(device)
 | 
			
		||||
        if not resolved:
 | 
			
		||||
            _LOGGER.error("All specified devices: %s could not be resolved.", defaults)
 | 
			
		||||
        return resolved
 | 
			
		||||
 | 
			
		||||
    # No devices specified, show interactive chooser
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ void AGS10Component::dump_config() {
 | 
			
		||||
bool AGS10Component::new_i2c_address(uint8_t newaddress) {
 | 
			
		||||
  uint8_t rev_newaddress = ~newaddress;
 | 
			
		||||
  std::array<uint8_t, 5> data{newaddress, rev_newaddress, newaddress, rev_newaddress, 0};
 | 
			
		||||
  data[4] = calc_crc8_(data, 4);
 | 
			
		||||
  data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
 | 
			
		||||
  if (!this->write_bytes(REG_ADDRESS, data)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
@@ -109,7 +109,7 @@ bool AGS10Component::set_zero_point_with_current_resistance() { return this->set
 | 
			
		||||
 | 
			
		||||
bool AGS10Component::set_zero_point_with(uint16_t value) {
 | 
			
		||||
  std::array<uint8_t, 5> data{0x00, 0x0C, (uint8_t) ((value >> 8) & 0xFF), (uint8_t) (value & 0xFF), 0};
 | 
			
		||||
  data[4] = calc_crc8_(data, 4);
 | 
			
		||||
  data[4] = crc8(data.data(), 4, 0xFF, 0x31, true);
 | 
			
		||||
  if (!this->write_bytes(REG_CALIBRATION, data)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
@@ -184,7 +184,7 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
 | 
			
		||||
  auto res = *data;
 | 
			
		||||
  auto crc_byte = res[len];
 | 
			
		||||
 | 
			
		||||
  if (crc_byte != calc_crc8_(res, len)) {
 | 
			
		||||
  if (crc_byte != crc8(res.data(), len, 0xFF, 0x31, true)) {
 | 
			
		||||
    this->error_code_ = CRC_CHECK_FAILED;
 | 
			
		||||
    ESP_LOGE(TAG, "Reading AGS10 version failed: crc error!");
 | 
			
		||||
    return optional<std::array<uint8_t, N>>();
 | 
			
		||||
@@ -192,20 +192,5 @@ template<size_t N> optional<std::array<uint8_t, N>> AGS10Component::read_and_che
 | 
			
		||||
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<size_t N> uint8_t AGS10Component::calc_crc8_(std::array<uint8_t, N> dat, uint8_t num) {
 | 
			
		||||
  uint8_t i, byte1, crc = 0xFF;
 | 
			
		||||
  for (byte1 = 0; byte1 < num; byte1++) {
 | 
			
		||||
    crc ^= (dat[byte1]);
 | 
			
		||||
    for (i = 0; i < 8; i++) {
 | 
			
		||||
      if (crc & 0x80) {
 | 
			
		||||
        crc = (crc << 1) ^ 0x31;
 | 
			
		||||
      } else {
 | 
			
		||||
        crc = (crc << 1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return crc;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace ags10
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/automation.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace ags10 {
 | 
			
		||||
@@ -99,16 +99,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
   * Read, checks and returns data from the sensor.
 | 
			
		||||
   */
 | 
			
		||||
  template<size_t N> optional<std::array<uint8_t, N>> read_and_check_(uint8_t a_register);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculates CRC8 value.
 | 
			
		||||
   *
 | 
			
		||||
   * CRC8 calculation, initial value: 0xFF, polynomial: 0x31 (x8+ x5+ x4+1)
 | 
			
		||||
   *
 | 
			
		||||
   * @param[in] dat the data buffer
 | 
			
		||||
   * @param num number of bytes in the buffer
 | 
			
		||||
   */
 | 
			
		||||
  template<size_t N> uint8_t calc_crc8_(std::array<uint8_t, N> dat, uint8_t num);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class AGS10NewI2cAddressAction : public Action<Ts...>, public Parented<AGS10Component> {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,6 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    yield esp32_ble_tracker.register_ble_device(var, config)
 | 
			
		||||
    await esp32_ble_tracker.register_ble_device(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -29,22 +29,6 @@ namespace am2315c {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "am2315c";
 | 
			
		||||
 | 
			
		||||
uint8_t AM2315C::crc8_(uint8_t *data, uint8_t len) {
 | 
			
		||||
  uint8_t crc = 0xFF;
 | 
			
		||||
  while (len--) {
 | 
			
		||||
    crc ^= *data++;
 | 
			
		||||
    for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
      if (crc & 0x80) {
 | 
			
		||||
        crc <<= 1;
 | 
			
		||||
        crc ^= 0x31;
 | 
			
		||||
      } else {
 | 
			
		||||
        crc <<= 1;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return crc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AM2315C::reset_register_(uint8_t reg) {
 | 
			
		||||
  //  code based on demo code sent by www.aosong.com
 | 
			
		||||
  //  no further documentation.
 | 
			
		||||
@@ -86,7 +70,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
 | 
			
		||||
  humidity = raw * 9.5367431640625e-5;
 | 
			
		||||
  raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
 | 
			
		||||
  temperature = raw * 1.9073486328125e-4 - 50;
 | 
			
		||||
  return this->crc8_(data, 6) == data[6];
 | 
			
		||||
  return crc8(data, 6, 0xFF, 0x31, true) == data[6];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AM2315C::setup() {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,9 @@
 | 
			
		||||
// SOFTWARE.
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace am2315c {
 | 
			
		||||
@@ -39,7 +39,6 @@ class AM2315C : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t crc8_(uint8_t *data, uint8_t len);
 | 
			
		||||
  bool convert_(uint8_t *data, float &humidity, float &temperature);
 | 
			
		||||
  bool reset_register_(uint8_t reg);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -321,6 +321,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
 | 
			
		||||
    HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
 | 
			
		||||
)
 | 
			
		||||
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
 | 
			
		||||
    serv = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg, serv, True)
 | 
			
		||||
    cg.add(var.set_service("esphome.tag_scanned"))
 | 
			
		||||
 
 | 
			
		||||
@@ -289,16 +289,26 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
 | 
			
		||||
    return 0;  // Doesn't fit
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Allocate buffer space - pass payload size, allocation functions add header/footer space
 | 
			
		||||
  ProtoWriteBuffer buffer = is_single ? conn->allocate_single_message_buffer(calculated_size)
 | 
			
		||||
                                      : conn->allocate_batch_message_buffer(calculated_size);
 | 
			
		||||
 | 
			
		||||
  // Get buffer size after allocation (which includes header padding)
 | 
			
		||||
  std::vector<uint8_t> &shared_buf = conn->parent_->get_shared_buffer_ref();
 | 
			
		||||
  size_t size_before_encode = shared_buf.size();
 | 
			
		||||
 | 
			
		||||
  if (is_single || conn->flags_.batch_first_message) {
 | 
			
		||||
    // Single message or first batch message
 | 
			
		||||
    conn->prepare_first_message_buffer(shared_buf, header_padding, total_calculated_size);
 | 
			
		||||
    if (conn->flags_.batch_first_message) {
 | 
			
		||||
      conn->flags_.batch_first_message = false;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // Batch message second or later
 | 
			
		||||
    // Add padding for previous message footer + this message header
 | 
			
		||||
    size_t current_size = shared_buf.size();
 | 
			
		||||
    shared_buf.reserve(current_size + total_calculated_size);
 | 
			
		||||
    shared_buf.resize(current_size + footer_size + header_padding);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Encode directly into buffer
 | 
			
		||||
  msg.encode(buffer);
 | 
			
		||||
  size_t size_before_encode = shared_buf.size();
 | 
			
		||||
  msg.encode({&shared_buf});
 | 
			
		||||
 | 
			
		||||
  // Calculate actual encoded size (not including header that was already added)
 | 
			
		||||
  size_t actual_payload_size = shared_buf.size() - size_before_encode;
 | 
			
		||||
@@ -455,9 +465,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
 | 
			
		||||
  resp.cold_white = values.get_cold_white();
 | 
			
		||||
  resp.warm_white = values.get_warm_white();
 | 
			
		||||
  if (light->supports_effects()) {
 | 
			
		||||
    // get_effect_name() returns temporary std::string - must store it
 | 
			
		||||
    std::string effect_name = light->get_effect_name();
 | 
			
		||||
    resp.set_effect(StringRef(effect_name));
 | 
			
		||||
    resp.set_effect(light->get_effect_name_ref());
 | 
			
		||||
  }
 | 
			
		||||
  return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
 | 
			
		||||
}
 | 
			
		||||
@@ -1415,9 +1423,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
 | 
			
		||||
  static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
 | 
			
		||||
  resp.set_esphome_version(ESPHOME_VERSION_REF);
 | 
			
		||||
 | 
			
		||||
  // get_compilation_time() returns temporary std::string - must store it
 | 
			
		||||
  std::string compilation_time = App.get_compilation_time();
 | 
			
		||||
  resp.set_compilation_time(StringRef(compilation_time));
 | 
			
		||||
  resp.set_compilation_time(App.get_compilation_time_ref());
 | 
			
		||||
 | 
			
		||||
  // Compile-time StringRef constants for manufacturers
 | 
			
		||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
 | 
			
		||||
@@ -1620,14 +1626,6 @@ bool APIConnection::schedule_batch_() {
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ProtoWriteBuffer APIConnection::allocate_single_message_buffer(uint16_t size) { return this->create_buffer(size); }
 | 
			
		||||
 | 
			
		||||
ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
 | 
			
		||||
  ProtoWriteBuffer result = this->prepare_message_buffer(size, this->flags_.batch_first_message);
 | 
			
		||||
  this->flags_.batch_first_message = false;
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void APIConnection::process_batch_() {
 | 
			
		||||
  // Ensure PacketInfo remains trivially destructible for our placement new approach
 | 
			
		||||
  static_assert(std::is_trivially_destructible<PacketInfo>::value,
 | 
			
		||||
@@ -1735,7 +1733,7 @@ void APIConnection::process_batch_() {
 | 
			
		||||
    }
 | 
			
		||||
    remaining_size -= payload_size;
 | 
			
		||||
    // Calculate where the next message's header padding will start
 | 
			
		||||
    // Current buffer size + footer space (that prepare_message_buffer will add for this message)
 | 
			
		||||
    // Current buffer size + footer space for this message
 | 
			
		||||
    current_offset = shared_buf.size() + footer_size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ static constexpr size_t MAX_PACKETS_PER_BATCH = 64;  // ESP32 has 8KB+ stack, HO
 | 
			
		||||
static constexpr size_t MAX_PACKETS_PER_BATCH = 32;  // ESP8266/RP2040/etc have smaller stacks
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class APIConnection : public APIServerConnection {
 | 
			
		||||
class APIConnection final : public APIServerConnection {
 | 
			
		||||
 public:
 | 
			
		||||
  friend class APIServer;
 | 
			
		||||
  friend class ListEntitiesIterator;
 | 
			
		||||
@@ -252,44 +252,21 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
 | 
			
		||||
    // Get header padding size - used for both reserve and insert
 | 
			
		||||
    uint8_t header_padding = this->helper_->frame_header_padding();
 | 
			
		||||
 | 
			
		||||
    // Get shared buffer from parent server
 | 
			
		||||
    std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
 | 
			
		||||
    this->prepare_first_message_buffer(shared_buf, header_padding,
 | 
			
		||||
                                       reserve_size + header_padding + this->helper_->frame_footer_size());
 | 
			
		||||
    return {&shared_buf};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void prepare_first_message_buffer(std::vector<uint8_t> &shared_buf, size_t header_padding, size_t total_size) {
 | 
			
		||||
    shared_buf.clear();
 | 
			
		||||
    // Reserve space for header padding + message + footer
 | 
			
		||||
    // - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
 | 
			
		||||
    // - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
 | 
			
		||||
    shared_buf.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
 | 
			
		||||
    shared_buf.reserve(total_size);
 | 
			
		||||
    // Resize to add header padding so message encoding starts at the correct position
 | 
			
		||||
    shared_buf.resize(header_padding);
 | 
			
		||||
    return {&shared_buf};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Prepare buffer for next message in batch
 | 
			
		||||
  ProtoWriteBuffer prepare_message_buffer(uint16_t message_size, bool is_first_message) {
 | 
			
		||||
    // Get reference to shared buffer (it maintains state between batch messages)
 | 
			
		||||
    std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
 | 
			
		||||
 | 
			
		||||
    if (is_first_message) {
 | 
			
		||||
      shared_buf.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t current_size = shared_buf.size();
 | 
			
		||||
 | 
			
		||||
    // Calculate padding to add:
 | 
			
		||||
    // - First message: just header padding
 | 
			
		||||
    // - Subsequent messages: footer for previous message + header padding for this message
 | 
			
		||||
    size_t padding_to_add = is_first_message
 | 
			
		||||
                                ? this->helper_->frame_header_padding()
 | 
			
		||||
                                : this->helper_->frame_header_padding() + this->helper_->frame_footer_size();
 | 
			
		||||
 | 
			
		||||
    // Reserve space for padding + message
 | 
			
		||||
    shared_buf.reserve(current_size + padding_to_add + message_size);
 | 
			
		||||
 | 
			
		||||
    // Resize to add the padding bytes
 | 
			
		||||
    shared_buf.resize(current_size + padding_to_add);
 | 
			
		||||
 | 
			
		||||
    return {&shared_buf};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool try_to_clear_buffer(bool log_out_of_space);
 | 
			
		||||
@@ -297,10 +274,6 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
 | 
			
		||||
  std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
 | 
			
		||||
 | 
			
		||||
  // Buffer allocator methods for batch processing
 | 
			
		||||
  ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
 | 
			
		||||
  ProtoWriteBuffer allocate_batch_message_buffer(uint16_t size);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // Helper function to handle authentication completion
 | 
			
		||||
  void complete_authentication_();
 | 
			
		||||
@@ -328,9 +301,17 @@ class APIConnection : public APIServerConnection {
 | 
			
		||||
                                              APIConnection *conn, uint32_t remaining_size, bool is_single) {
 | 
			
		||||
    // Set common fields that are shared by all entity types
 | 
			
		||||
    msg.key = entity->get_object_id_hash();
 | 
			
		||||
    // IMPORTANT: get_object_id() may return a temporary std::string
 | 
			
		||||
    std::string object_id = entity->get_object_id();
 | 
			
		||||
    msg.set_object_id(StringRef(object_id));
 | 
			
		||||
    // Try to use static reference first to avoid allocation
 | 
			
		||||
    StringRef static_ref = entity->get_object_id_ref_for_api_();
 | 
			
		||||
    // Store dynamic string outside the if-else to maintain lifetime
 | 
			
		||||
    std::string object_id;
 | 
			
		||||
    if (!static_ref.empty()) {
 | 
			
		||||
      msg.set_object_id(static_ref);
 | 
			
		||||
    } else {
 | 
			
		||||
      // Dynamic case - need to allocate
 | 
			
		||||
      object_id = entity->get_object_id();
 | 
			
		||||
      msg.set_object_id(StringRef(object_id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (entity->has_own_name()) {
 | 
			
		||||
      msg.set_name(entity->get_name());
 | 
			
		||||
 
 | 
			
		||||
@@ -104,9 +104,9 @@ class APIFrameHelper {
 | 
			
		||||
  // The buffer contains all messages with appropriate padding before each
 | 
			
		||||
  virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
 | 
			
		||||
  // Get the frame header padding required by this protocol
 | 
			
		||||
  virtual uint8_t frame_header_padding() = 0;
 | 
			
		||||
  uint8_t frame_header_padding() const { return frame_header_padding_; }
 | 
			
		||||
  // Get the frame footer size required by this protocol
 | 
			
		||||
  virtual uint8_t frame_footer_size() = 0;
 | 
			
		||||
  uint8_t frame_footer_size() const { return frame_footer_size_; }
 | 
			
		||||
  // Check if socket has data ready to read
 | 
			
		||||
  bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
 | 
			
		||||
namespace esphome::api {
 | 
			
		||||
 | 
			
		||||
class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
class APINoiseFrameHelper final : public APIFrameHelper {
 | 
			
		||||
 public:
 | 
			
		||||
  APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
 | 
			
		||||
                      const ClientInfo *client_info)
 | 
			
		||||
@@ -25,10 +25,6 @@ class APINoiseFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
 | 
			
		||||
  APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
 | 
			
		||||
  // Get the frame header padding required by this protocol
 | 
			
		||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
			
		||||
  // Get the frame footer size required by this protocol
 | 
			
		||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  APIError state_action_();
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
 | 
			
		||||
namespace esphome::api {
 | 
			
		||||
 | 
			
		||||
class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
class APIPlaintextFrameHelper final : public APIFrameHelper {
 | 
			
		||||
 public:
 | 
			
		||||
  APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
 | 
			
		||||
      : APIFrameHelper(std::move(socket), client_info) {
 | 
			
		||||
@@ -22,9 +22,6 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
 | 
			
		||||
  APIError read_packet(ReadPacketBuffer *buffer) override;
 | 
			
		||||
  APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
 | 
			
		||||
  APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
 | 
			
		||||
  uint8_t frame_header_padding() override { return frame_header_padding_; }
 | 
			
		||||
  // Get the frame footer size required by this protocol
 | 
			
		||||
  uint8_t frame_footer_size() override { return frame_footer_size_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  APIError try_read_frame_(std::vector<uint8_t> *frame);
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -8,74 +8,70 @@ namespace esphome::api {
 | 
			
		||||
static const char *const TAG = "api.proto";
 | 
			
		||||
 | 
			
		||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
 | 
			
		||||
  uint32_t i = 0;
 | 
			
		||||
  bool error = false;
 | 
			
		||||
  while (i < length) {
 | 
			
		||||
  const uint8_t *ptr = buffer;
 | 
			
		||||
  const uint8_t *end = buffer + length;
 | 
			
		||||
 | 
			
		||||
  while (ptr < end) {
 | 
			
		||||
    uint32_t consumed;
 | 
			
		||||
    auto res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
			
		||||
 | 
			
		||||
    // Parse field header
 | 
			
		||||
    auto res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
 | 
			
		||||
    if (!res.has_value()) {
 | 
			
		||||
      ESP_LOGV(TAG, "Invalid field start at %" PRIu32, i);
 | 
			
		||||
      break;
 | 
			
		||||
      ESP_LOGV(TAG, "Invalid field start at offset %ld", (long) (ptr - buffer));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint32_t field_type = (res->as_uint32()) & 0b111;
 | 
			
		||||
    uint32_t field_id = (res->as_uint32()) >> 3;
 | 
			
		||||
    i += consumed;
 | 
			
		||||
    uint32_t tag = res->as_uint32();
 | 
			
		||||
    uint32_t field_type = tag & 0b111;
 | 
			
		||||
    uint32_t field_id = tag >> 3;
 | 
			
		||||
    ptr += consumed;
 | 
			
		||||
 | 
			
		||||
    switch (field_type) {
 | 
			
		||||
      case 0: {  // VarInt
 | 
			
		||||
        res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
			
		||||
        res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
 | 
			
		||||
        if (!res.has_value()) {
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid VarInt at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid VarInt at offset %ld", (long) (ptr - buffer));
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this->decode_varint(field_id, *res)) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode VarInt field %" PRIu32 " with value %" PRIu32 "!", field_id, res->as_uint32());
 | 
			
		||||
        }
 | 
			
		||||
        i += consumed;
 | 
			
		||||
        ptr += consumed;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 2: {  // Length-delimited
 | 
			
		||||
        res = ProtoVarInt::parse(&buffer[i], length - i, &consumed);
 | 
			
		||||
        res = ProtoVarInt::parse(ptr, end - ptr, &consumed);
 | 
			
		||||
        if (!res.has_value()) {
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid Length Delimited at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
          ESP_LOGV(TAG, "Invalid Length Delimited at offset %ld", (long) (ptr - buffer));
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        uint32_t field_length = res->as_uint32();
 | 
			
		||||
        i += consumed;
 | 
			
		||||
        if (field_length > length - i) {
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Length Delimited at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        ptr += consumed;
 | 
			
		||||
        if (ptr + field_length > end) {
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this->decode_length(field_id, ProtoLengthDelimited(&buffer[i], field_length))) {
 | 
			
		||||
        if (!this->decode_length(field_id, ProtoLengthDelimited(ptr, field_length))) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode Length Delimited field %" PRIu32 "!", field_id);
 | 
			
		||||
        }
 | 
			
		||||
        i += field_length;
 | 
			
		||||
        ptr += field_length;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 5: {  // 32-bit
 | 
			
		||||
        if (length - i < 4) {
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at %" PRIu32, i);
 | 
			
		||||
          error = true;
 | 
			
		||||
          break;
 | 
			
		||||
        if (ptr + 4 > end) {
 | 
			
		||||
          ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        uint32_t val = encode_uint32(buffer[i + 3], buffer[i + 2], buffer[i + 1], buffer[i]);
 | 
			
		||||
        uint32_t val = encode_uint32(ptr[3], ptr[2], ptr[1], ptr[0]);
 | 
			
		||||
        if (!this->decode_32bit(field_id, Proto32Bit(val))) {
 | 
			
		||||
          ESP_LOGV(TAG, "Cannot decode 32-bit field %" PRIu32 " with value %" PRIu32 "!", field_id, val);
 | 
			
		||||
        }
 | 
			
		||||
        i += 4;
 | 
			
		||||
        ptr += 4;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default:
 | 
			
		||||
        ESP_LOGV(TAG, "Invalid field type at %" PRIu32, i);
 | 
			
		||||
        error = true;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    if (error) {
 | 
			
		||||
      break;
 | 
			
		||||
        ESP_LOGV(TAG, "Invalid field type %u at offset %ld", field_type, (long) (ptr - buffer));
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
 | 
			
		||||
  i2c::ErrorCode err;
 | 
			
		||||
  uint8_t data[8]{};
 | 
			
		||||
 | 
			
		||||
  err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false);
 | 
			
		||||
  err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD));
 | 
			
		||||
  ERROR_CHECK(err);
 | 
			
		||||
  err = this->read(data, sizeof(data));
 | 
			
		||||
  ERROR_CHECK(err);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,19 @@ namespace binary_sensor {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "binary_sensor";
 | 
			
		||||
 | 
			
		||||
// Function implementation of LOG_BINARY_SENSOR macro to reduce code size
 | 
			
		||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) {
 | 
			
		||||
  if (obj == nullptr) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
 | 
			
		||||
 | 
			
		||||
  if (!obj->get_device_class().empty()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "%s  Device Class: '%s'", prefix, obj->get_device_class().c_str());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BinarySensor::publish_state(bool new_state) {
 | 
			
		||||
  if (this->filter_list_ == nullptr) {
 | 
			
		||||
    this->send_state_internal(new_state);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,13 +10,10 @@ namespace esphome {
 | 
			
		||||
 | 
			
		||||
namespace binary_sensor {
 | 
			
		||||
 | 
			
		||||
#define LOG_BINARY_SENSOR(prefix, type, obj) \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
 | 
			
		||||
    if (!(obj)->get_device_class().empty()) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
 | 
			
		||||
    } \
 | 
			
		||||
  }
 | 
			
		||||
class BinarySensor;
 | 
			
		||||
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
 | 
			
		||||
 | 
			
		||||
#define LOG_BINARY_SENSOR(prefix, type, obj) log_binary_sensor(TAG, prefix, LOG_STR_LITERAL(type), obj)
 | 
			
		||||
 | 
			
		||||
#define SUB_BINARY_SENSOR(name) \
 | 
			
		||||
 protected: \
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
 | 
			
		||||
        cg.add(
 | 
			
		||||
@@ -63,6 +63,6 @@ def to_code(config):
 | 
			
		||||
        )
 | 
			
		||||
        cg.add(var.set_char_uuid128(uuid128))
 | 
			
		||||
    cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
 | 
			
		||||
    yield output.register_output(var, config)
 | 
			
		||||
    yield ble_client.register_ble_node(var, config)
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    await output.register_output(var, config)
 | 
			
		||||
    await ble_client.register_ble_node(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ namespace esphome::bluetooth_proxy {
 | 
			
		||||
 | 
			
		||||
class BluetoothProxy;
 | 
			
		||||
 | 
			
		||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
 | 
			
		||||
class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
 | 
			
		||||
 public:
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
 | 
			
		||||
  SUBSCRIPTION_RAW_ADVERTISEMENTS = 1 << 0,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
			
		||||
class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
 | 
			
		||||
  friend class BluetoothConnection;  // Allow connection to update connections_free_response_
 | 
			
		||||
 public:
 | 
			
		||||
  BluetoothProxy();
 | 
			
		||||
 
 | 
			
		||||
@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
 | 
			
		||||
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
 | 
			
		||||
  uint8_t raw_data[len * 2];
 | 
			
		||||
  // read using read_register because we have little-endian data, and read_bytes_16 will swap it
 | 
			
		||||
  i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true);
 | 
			
		||||
  i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2);
 | 
			
		||||
  if (err != i2c::ERROR_OK) {
 | 
			
		||||
    return err;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -63,12 +63,12 @@ void BMP280Component::setup() {
 | 
			
		||||
 | 
			
		||||
  // Read the chip id twice, to work around a bug where the first read is 0.
 | 
			
		||||
  // https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
 | 
			
		||||
  if (!this->read_byte(0xD0, &chip_id)) {
 | 
			
		||||
  if (!this->bmp_read_byte(0xD0, &chip_id)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (!this->read_byte(0xD0, &chip_id)) {
 | 
			
		||||
  if (!this->bmp_read_byte(0xD0, &chip_id)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
 | 
			
		||||
    return;
 | 
			
		||||
@@ -80,7 +80,7 @@ void BMP280Component::setup() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Send a soft reset.
 | 
			
		||||
  if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
 | 
			
		||||
  if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
 | 
			
		||||
    this->mark_failed("Reset failed");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -89,7 +89,7 @@ void BMP280Component::setup() {
 | 
			
		||||
  uint8_t retry = 5;
 | 
			
		||||
  do {
 | 
			
		||||
    delay(2);
 | 
			
		||||
    if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
 | 
			
		||||
    if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
 | 
			
		||||
      this->mark_failed("Error reading status register");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -115,14 +115,14 @@ void BMP280Component::setup() {
 | 
			
		||||
  this->calibration_.p9 = this->read_s16_le_(0x9E);
 | 
			
		||||
 | 
			
		||||
  uint8_t config_register = 0;
 | 
			
		||||
  if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
 | 
			
		||||
  if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
 | 
			
		||||
    this->mark_failed("Read config");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  config_register &= ~0b11111100;
 | 
			
		||||
  config_register |= 0b000 << 5;  // 0.5 ms standby time
 | 
			
		||||
  config_register |= (this->iir_filter_ & 0b111) << 2;
 | 
			
		||||
  if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
 | 
			
		||||
  if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
 | 
			
		||||
    this->mark_failed("Write config");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -159,7 +159,7 @@ void BMP280Component::update() {
 | 
			
		||||
  meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
 | 
			
		||||
  meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
 | 
			
		||||
  meas_value |= 0b01;  // Forced mode
 | 
			
		||||
  if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
 | 
			
		||||
  if (!this->bmp_write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -188,9 +188,10 @@ void BMP280Component::update() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float BMP280Component::read_temperature_(int32_t *t_fine) {
 | 
			
		||||
  uint8_t data[3];
 | 
			
		||||
  if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
 | 
			
		||||
  uint8_t data[3]{};
 | 
			
		||||
  if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
 | 
			
		||||
    return NAN;
 | 
			
		||||
  ESP_LOGV(TAG, "Read temperature data, raw: %02X %02X %02X", data[0], data[1], data[2]);
 | 
			
		||||
  int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
 | 
			
		||||
  adc >>= 4;
 | 
			
		||||
  if (adc == 0x80000) {
 | 
			
		||||
@@ -212,7 +213,7 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
 | 
			
		||||
 | 
			
		||||
float BMP280Component::read_pressure_(int32_t t_fine) {
 | 
			
		||||
  uint8_t data[3];
 | 
			
		||||
  if (!this->read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
 | 
			
		||||
  if (!this->bmp_read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
 | 
			
		||||
    return NAN;
 | 
			
		||||
  int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
 | 
			
		||||
  adc >>= 4;
 | 
			
		||||
@@ -258,12 +259,12 @@ void BMP280Component::set_pressure_oversampling(BMP280Oversampling pressure_over
 | 
			
		||||
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
 | 
			
		||||
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
 | 
			
		||||
  uint8_t data = 0;
 | 
			
		||||
  this->read_byte(a_register, &data);
 | 
			
		||||
  this->bmp_read_byte(a_register, &data);
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
 | 
			
		||||
  uint16_t data = 0;
 | 
			
		||||
  this->read_byte_16(a_register, &data);
 | 
			
		||||
  this->bmp_read_byte_16(a_register, &data);
 | 
			
		||||
  return (data >> 8) | (data << 8);
 | 
			
		||||
}
 | 
			
		||||
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
 | 
			
		||||
 
 | 
			
		||||
@@ -67,12 +67,12 @@ class BMP280Component : public PollingComponent {
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
  virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
 | 
			
		||||
  virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
 | 
			
		||||
  virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
 | 
			
		||||
  virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
 | 
			
		||||
  virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
 | 
			
		||||
  virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
 | 
			
		||||
  virtual bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) = 0;
 | 
			
		||||
 | 
			
		||||
  /// Read the temperature value and store the calculated ambient temperature in t_fine.
 | 
			
		||||
  float read_temperature_(int32_t *t_fine);
 | 
			
		||||
  /// Read the pressure value in hPa using the provided t_fine value.
 | 
			
		||||
 
 | 
			
		||||
@@ -5,19 +5,6 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace bmp280_i2c {
 | 
			
		||||
 | 
			
		||||
bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) {
 | 
			
		||||
  return I2CDevice::read_byte(a_register, data);
 | 
			
		||||
};
 | 
			
		||||
bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
 | 
			
		||||
  return I2CDevice::write_byte(a_register, data);
 | 
			
		||||
};
 | 
			
		||||
bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
 | 
			
		||||
  return I2CDevice::read_bytes(a_register, data, len);
 | 
			
		||||
};
 | 
			
		||||
bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
 | 
			
		||||
  return I2CDevice::read_byte_16(a_register, data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void BMP280I2CComponent::dump_config() {
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  BMP280Component::dump_config();
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,12 @@ static const char *const TAG = "bmp280_i2c.sensor";
 | 
			
		||||
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
 | 
			
		||||
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  bool read_byte(uint8_t a_register, uint8_t *data) override;
 | 
			
		||||
  bool write_byte(uint8_t a_register, uint8_t data) override;
 | 
			
		||||
  bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
 | 
			
		||||
  bool read_byte_16(uint8_t a_register, uint16_t *data) override;
 | 
			
		||||
  bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
 | 
			
		||||
  bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
 | 
			
		||||
  bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
 | 
			
		||||
    return read_bytes(a_register, data, len);
 | 
			
		||||
  }
 | 
			
		||||
  bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override { return read_byte_16(a_register, data); }
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ void BMP280SPIComponent::setup() {
 | 
			
		||||
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
 | 
			
		||||
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
 | 
			
		||||
 | 
			
		||||
bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
 | 
			
		||||
bool BMP280SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(set_bit(a_register, 7));
 | 
			
		||||
  *data = this->transfer_byte(0);
 | 
			
		||||
@@ -36,7 +36,7 @@ bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
 | 
			
		||||
bool BMP280SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(clear_bit(a_register, 7));
 | 
			
		||||
  this->transfer_byte(data);
 | 
			
		||||
@@ -44,7 +44,7 @@ bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
 | 
			
		||||
bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(set_bit(a_register, 7));
 | 
			
		||||
  this->read_array(data, len);
 | 
			
		||||
@@ -52,7 +52,7 @@ bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t le
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
 | 
			
		||||
bool BMP280SPIComponent::bmp_read_byte_16(uint8_t a_register, uint16_t *data) {
 | 
			
		||||
  this->enable();
 | 
			
		||||
  this->transfer_byte(set_bit(a_register, 7));
 | 
			
		||||
  ((uint8_t *) data)[1] = this->transfer_byte(0);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,10 @@ class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
 | 
			
		||||
                           public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
 | 
			
		||||
                                                 spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  bool read_byte(uint8_t a_register, uint8_t *data) override;
 | 
			
		||||
  bool write_byte(uint8_t a_register, uint8_t data) override;
 | 
			
		||||
  bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
 | 
			
		||||
  bool read_byte_16(uint8_t a_register, uint16_t *data) override;
 | 
			
		||||
  bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
 | 
			
		||||
  bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
 | 
			
		||||
  bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
 | 
			
		||||
  bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace bmp280_spi
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,19 @@ namespace button {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "button";
 | 
			
		||||
 | 
			
		||||
// Function implementation of LOG_BUTTON macro to reduce code size
 | 
			
		||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj) {
 | 
			
		||||
  if (obj == nullptr) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
 | 
			
		||||
 | 
			
		||||
  if (!obj->get_icon().empty()) {
 | 
			
		||||
    ESP_LOGCONFIG(tag, "%s  Icon: '%s'", prefix, obj->get_icon().c_str());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Button::press() {
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str());
 | 
			
		||||
  this->press_action();
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,10 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace button {
 | 
			
		||||
 | 
			
		||||
#define LOG_BUTTON(prefix, type, obj) \
 | 
			
		||||
  if ((obj) != nullptr) { \
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
 | 
			
		||||
    if (!(obj)->get_icon().empty()) { \
 | 
			
		||||
      ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
 | 
			
		||||
    } \
 | 
			
		||||
  }
 | 
			
		||||
class Button;
 | 
			
		||||
void log_button(const char *tag, const char *prefix, const char *type, Button *obj);
 | 
			
		||||
 | 
			
		||||
#define LOG_BUTTON(prefix, type, obj) log_button(TAG, prefix, LOG_STR_LITERAL(type), obj)
 | 
			
		||||
 | 
			
		||||
#define SUB_BUTTON(name) \
 | 
			
		||||
 protected: \
 | 
			
		||||
 
 | 
			
		||||
@@ -153,8 +153,8 @@ void CCS811Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "CCS811");
 | 
			
		||||
  LOG_I2C_DEVICE(this)
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this)
 | 
			
		||||
  LOG_SENSOR("  ", "CO2 Sensor", this->co2_)
 | 
			
		||||
  LOG_SENSOR("  ", "TVOC Sensor", this->tvoc_)
 | 
			
		||||
  LOG_SENSOR("  ", "CO2 Sensor", this->co2_);
 | 
			
		||||
  LOG_SENSOR("  ", "TVOC Sensor", this->tvoc_);
 | 
			
		||||
  LOG_TEXT_SENSOR("  ", "Firmware Version Sensor", this->version_)
 | 
			
		||||
  if (this->baseline_) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Baseline: %04X", *this->baseline_);
 | 
			
		||||
 
 | 
			
		||||
@@ -91,7 +91,7 @@ bool CH422GComponent::read_inputs_() {
 | 
			
		||||
 | 
			
		||||
// Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
 | 
			
		||||
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
 | 
			
		||||
  auto err = this->bus_->write(reg, &value, 1);
 | 
			
		||||
  auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
 | 
			
		||||
  if (err != i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
 | 
			
		||||
    return false;
 | 
			
		||||
@@ -102,7 +102,7 @@ bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
 | 
			
		||||
 | 
			
		||||
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
 | 
			
		||||
  uint8_t value;
 | 
			
		||||
  auto err = this->bus_->read(reg, &value, 1);
 | 
			
		||||
  auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
 | 
			
		||||
  if (err != i2c::ERROR_OK) {
 | 
			
		||||
    this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
 | 
			
		||||
    return 0;
 | 
			
		||||
 
 | 
			
		||||
@@ -327,7 +327,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb
 | 
			
		||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
 | 
			
		||||
 | 
			
		||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
 | 
			
		||||
  this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^
 | 
			
		||||
  this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_preference_hash() ^
 | 
			
		||||
                                                                              RESTORE_STATE_VERSION);
 | 
			
		||||
  ClimateDeviceRestoreState recovered{};
 | 
			
		||||
  if (!this->rtc_.load(&recovered))
 | 
			
		||||
 
 | 
			
		||||
@@ -228,9 +228,9 @@ async def cover_stop_to_code(config, action_id, template_arg, args):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@automation.register_action("cover.toggle", ToggleAction, COVER_ACTION_SCHEMA)
 | 
			
		||||
def cover_toggle_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = yield cg.get_variable(config[CONF_ID])
 | 
			
		||||
    yield cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
async def cover_toggle_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    paren = await cg.get_variable(config[CONF_ID])
 | 
			
		||||
    return cg.new_Pvariable(action_id, template_arg, paren)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
COVER_CONTROL_ACTION_SCHEMA = cv.Schema(
 | 
			
		||||
 
 | 
			
		||||
@@ -194,7 +194,7 @@ void Cover::publish_state(bool save) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
optional<CoverRestoreState> Cover::restore_state_() {
 | 
			
		||||
  this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_object_id_hash());
 | 
			
		||||
  this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_preference_hash());
 | 
			
		||||
  CoverRestoreState recovered{};
 | 
			
		||||
  if (!this->rtc_.load(&recovered))
 | 
			
		||||
    return {};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include "soc/soc_caps.h"
 | 
			
		||||
#include "driver/gpio.h"
 | 
			
		||||
#include "deep_sleep_component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
@@ -83,7 +84,11 @@ void DeepSleepComponent::deep_sleep_() {
 | 
			
		||||
    }
 | 
			
		||||
    gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
 | 
			
		||||
    gpio_hold_en(gpio_pin);
 | 
			
		||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
 | 
			
		||||
    // Some ESP32 variants support holding a single GPIO during deep sleep without this function
 | 
			
		||||
    // For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
 | 
			
		||||
    gpio_deep_sleep_hold_en();
 | 
			
		||||
#endif
 | 
			
		||||
    bool level = !this->wakeup_pin_->is_inverted();
 | 
			
		||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
 | 
			
		||||
      level = !level;
 | 
			
		||||
@@ -120,7 +125,11 @@ void DeepSleepComponent::deep_sleep_() {
 | 
			
		||||
    }
 | 
			
		||||
    gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
 | 
			
		||||
    gpio_hold_en(gpio_pin);
 | 
			
		||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
 | 
			
		||||
    // Some ESP32 variants support holding a single GPIO during deep sleep without this function
 | 
			
		||||
    // For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
 | 
			
		||||
    gpio_deep_sleep_hold_en();
 | 
			
		||||
#endif
 | 
			
		||||
    bool level = !this->wakeup_pin_->is_inverted();
 | 
			
		||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
 | 
			
		||||
      level = !level;
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ void DutyTimeSensor::setup() {
 | 
			
		||||
  uint32_t seconds = 0;
 | 
			
		||||
 | 
			
		||||
  if (this->restore_) {
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash());
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<uint32_t>(this->get_preference_hash());
 | 
			
		||||
    this->pref_.load(&seconds);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -83,7 +83,7 @@ void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
 | 
			
		||||
  crc16 = calc_crc16_(address, 6);
 | 
			
		||||
  address[5] = crc16 & 0xFF;
 | 
			
		||||
  address[6] = (crc16 >> 8) & 0xFF;
 | 
			
		||||
  this->write(address, 7, true);
 | 
			
		||||
  this->write(address, 7);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float EE895Component::read_float_() {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32 {
 | 
			
		||||
@@ -156,20 +157,23 @@ class ESP32Preferences : public ESPPreferences {
 | 
			
		||||
    return failed == 0;
 | 
			
		||||
  }
 | 
			
		||||
  bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
 | 
			
		||||
    NVSData stored_data{};
 | 
			
		||||
    size_t actual_len;
 | 
			
		||||
    esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len);
 | 
			
		||||
    if (err != 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err));
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    stored_data.data.resize(actual_len);
 | 
			
		||||
    err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.data.data(), &actual_len);
 | 
			
		||||
    // Check size first before allocating memory
 | 
			
		||||
    if (actual_len != to_save.data.size()) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    auto stored_data = std::make_unique<uint8_t[]>(actual_len);
 | 
			
		||||
    err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len);
 | 
			
		||||
    if (err != 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return to_save.data != stored_data.data;
 | 
			
		||||
    return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool reset() override {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,28 @@ import re
 | 
			
		||||
 | 
			
		||||
from esphome import automation
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
 | 
			
		||||
from esphome.components.esp32 import (
 | 
			
		||||
    VARIANT_ESP32C2,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32C5,
 | 
			
		||||
    VARIANT_ESP32C6,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
    add_idf_sdkconfig_option,
 | 
			
		||||
    const,
 | 
			
		||||
    get_esp32_variant,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ENABLE_ON_BOOT,
 | 
			
		||||
    CONF_ESPHOME,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_NAME_ADD_MAC_SUFFIX,
 | 
			
		||||
    CONF_TX_POWER,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE, TimePeriod
 | 
			
		||||
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
 | 
			
		||||
from esphome.cpp_types import MockObj
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
@@ -146,7 +163,8 @@ IO_CAPABILITY = {
 | 
			
		||||
 | 
			
		||||
esp_power_level_t = cg.global_ns.enum("esp_power_level_t")
 | 
			
		||||
 | 
			
		||||
TX_POWER_LEVELS = {
 | 
			
		||||
# Power level mappings for code generation - ESP32 classic
 | 
			
		||||
TX_POWER_LEVELS_ESP32 = {
 | 
			
		||||
    -12: esp_power_level_t.ESP_PWR_LVL_N12,
 | 
			
		||||
    -9: esp_power_level_t.ESP_PWR_LVL_N9,
 | 
			
		||||
    -6: esp_power_level_t.ESP_PWR_LVL_N6,
 | 
			
		||||
@@ -157,6 +175,53 @@ TX_POWER_LEVELS = {
 | 
			
		||||
    9: esp_power_level_t.ESP_PWR_LVL_P9,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Power level mappings for code generation - Extended variants
 | 
			
		||||
TX_POWER_LEVELS_EXT = {
 | 
			
		||||
    -24: esp_power_level_t.ESP_PWR_LVL_N24,
 | 
			
		||||
    -21: esp_power_level_t.ESP_PWR_LVL_N21,
 | 
			
		||||
    -18: esp_power_level_t.ESP_PWR_LVL_N18,
 | 
			
		||||
    -15: esp_power_level_t.ESP_PWR_LVL_N15,
 | 
			
		||||
    -12: esp_power_level_t.ESP_PWR_LVL_N12,
 | 
			
		||||
    -9: esp_power_level_t.ESP_PWR_LVL_N9,
 | 
			
		||||
    -6: esp_power_level_t.ESP_PWR_LVL_N6,
 | 
			
		||||
    -3: esp_power_level_t.ESP_PWR_LVL_N3,
 | 
			
		||||
    0: esp_power_level_t.ESP_PWR_LVL_N0,
 | 
			
		||||
    3: esp_power_level_t.ESP_PWR_LVL_P3,
 | 
			
		||||
    6: esp_power_level_t.ESP_PWR_LVL_P6,
 | 
			
		||||
    9: esp_power_level_t.ESP_PWR_LVL_P9,
 | 
			
		||||
    12: esp_power_level_t.ESP_PWR_LVL_P12,
 | 
			
		||||
    15: esp_power_level_t.ESP_PWR_LVL_P15,
 | 
			
		||||
    18: esp_power_level_t.ESP_PWR_LVL_P18,
 | 
			
		||||
    20: esp_power_level_t.ESP_PWR_LVL_P20,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_tx_power_levels() -> dict[str, MockObj]:
 | 
			
		||||
    variant = get_esp32_variant()
 | 
			
		||||
    if variant in [
 | 
			
		||||
        VARIANT_ESP32C2,
 | 
			
		||||
        VARIANT_ESP32C3,
 | 
			
		||||
        VARIANT_ESP32C5,
 | 
			
		||||
        VARIANT_ESP32C6,
 | 
			
		||||
        VARIANT_ESP32H2,
 | 
			
		||||
        VARIANT_ESP32S3,
 | 
			
		||||
    ]:
 | 
			
		||||
        return TX_POWER_LEVELS_EXT
 | 
			
		||||
    return TX_POWER_LEVELS_ESP32
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_tx_power(value: int) -> int:
 | 
			
		||||
    value = cv.decibel(value)
 | 
			
		||||
    power_levels = _get_tx_power_levels()
 | 
			
		||||
    if value not in power_levels:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"TX power {value}dBm is not valid. "
 | 
			
		||||
            f"Valid values are: {', '.join(str(v) + 'dBm' for v in sorted(power_levels.keys()))}"
 | 
			
		||||
        )
 | 
			
		||||
    # Return just the dBm value, we'll map it to enum in to_code
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ESP32BLE),
 | 
			
		||||
@@ -164,6 +229,7 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
 | 
			
		||||
            IO_CAPABILITY, lower=True
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_TX_POWER): validate_tx_power,
 | 
			
		||||
        cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
 | 
			
		||||
        cv.Optional(CONF_ADVERTISING, default=False): cv.boolean,
 | 
			
		||||
        cv.Optional(
 | 
			
		||||
@@ -254,6 +320,9 @@ async def to_code(config):
 | 
			
		||||
    cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
 | 
			
		||||
    if (name := config.get(CONF_NAME)) is not None:
 | 
			
		||||
        cg.add(var.set_name(name))
 | 
			
		||||
    if (tx_power := config.get(CONF_TX_POWER)) is not None:
 | 
			
		||||
        # The validation already returned the enum value
 | 
			
		||||
        cg.add(var.set_tx_power(_get_tx_power_levels()[tx_power]))
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
@@ -280,6 +349,10 @@ async def to_code(config):
 | 
			
		||||
            add_idf_sdkconfig_option(
 | 
			
		||||
                "CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
 | 
			
		||||
            )
 | 
			
		||||
            # Increase GATT client connection retry count for problematic devices
 | 
			
		||||
            # Default in ESP-IDF is 3, we increase to 10 for better reliability with
 | 
			
		||||
            # low-power/timing-sensitive devices
 | 
			
		||||
            add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
 | 
			
		||||
 | 
			
		||||
        # Set the maximum number of notification registrations
 | 
			
		||||
        # This controls how many BLE characteristics can have notifications enabled
 | 
			
		||||
 
 | 
			
		||||
@@ -212,6 +212,15 @@ bool ESP32BLE::ble_setup_() {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set TX power for all BLE operations (advertising, scanning, connections)
 | 
			
		||||
  err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, this->tx_power_);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
 | 
			
		||||
    // Continue anyway as this is not critical
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "BLE TX power set to level %d", this->tx_power_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // BLE takes some time to be fully set up, 200ms should be more than enough
 | 
			
		||||
  delay(200);  // NOLINT
 | 
			
		||||
 | 
			
		||||
@@ -520,11 +529,106 @@ void ESP32BLE::dump_config() {
 | 
			
		||||
        io_capability_s = "invalid";
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    // Convert TX power level to dBm for display
 | 
			
		||||
    int tx_power_dbm = 0;
 | 
			
		||||
#if defined(CONFIG_IDF_TARGET_ESP32)
 | 
			
		||||
    // ESP32 classic power levels (0-7)
 | 
			
		||||
    switch (this->tx_power_) {
 | 
			
		||||
      case 0:
 | 
			
		||||
        tx_power_dbm = -12;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N12
 | 
			
		||||
      case 1:
 | 
			
		||||
        tx_power_dbm = -9;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N9
 | 
			
		||||
      case 2:
 | 
			
		||||
        tx_power_dbm = -6;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N6
 | 
			
		||||
      case 3:
 | 
			
		||||
        tx_power_dbm = -3;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N3
 | 
			
		||||
      case 4:
 | 
			
		||||
        tx_power_dbm = 0;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N0
 | 
			
		||||
      case 5:
 | 
			
		||||
        tx_power_dbm = 3;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P3
 | 
			
		||||
      case 6:
 | 
			
		||||
        tx_power_dbm = 6;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P6
 | 
			
		||||
      case 7:
 | 
			
		||||
        tx_power_dbm = 9;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P9
 | 
			
		||||
      default:
 | 
			
		||||
        tx_power_dbm = 0;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
#elif defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || \
 | 
			
		||||
    defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || \
 | 
			
		||||
    defined(CONFIG_IDF_TARGET_ESP32S3)
 | 
			
		||||
    // Extended power levels for C2/C3/C5/C6/H2/S3 (0-15)
 | 
			
		||||
    switch (this->tx_power_) {
 | 
			
		||||
      case 0:
 | 
			
		||||
        tx_power_dbm = -24;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N24
 | 
			
		||||
      case 1:
 | 
			
		||||
        tx_power_dbm = -21;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N21
 | 
			
		||||
      case 2:
 | 
			
		||||
        tx_power_dbm = -18;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N18
 | 
			
		||||
      case 3:
 | 
			
		||||
        tx_power_dbm = -15;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N15
 | 
			
		||||
      case 4:
 | 
			
		||||
        tx_power_dbm = -12;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N12
 | 
			
		||||
      case 5:
 | 
			
		||||
        tx_power_dbm = -9;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N9
 | 
			
		||||
      case 6:
 | 
			
		||||
        tx_power_dbm = -6;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N6
 | 
			
		||||
      case 7:
 | 
			
		||||
        tx_power_dbm = -3;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N3
 | 
			
		||||
      case 8:
 | 
			
		||||
        tx_power_dbm = 0;
 | 
			
		||||
        break;  // ESP_PWR_LVL_N0
 | 
			
		||||
      case 9:
 | 
			
		||||
        tx_power_dbm = 3;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P3
 | 
			
		||||
      case 10:
 | 
			
		||||
        tx_power_dbm = 6;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P6
 | 
			
		||||
      case 11:
 | 
			
		||||
        tx_power_dbm = 9;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P9
 | 
			
		||||
      case 12:
 | 
			
		||||
        tx_power_dbm = 12;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P12
 | 
			
		||||
      case 13:
 | 
			
		||||
        tx_power_dbm = 15;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P15
 | 
			
		||||
      case 14:
 | 
			
		||||
        tx_power_dbm = 18;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P18
 | 
			
		||||
      case 15:
 | 
			
		||||
        tx_power_dbm = 20;
 | 
			
		||||
        break;  // ESP_PWR_LVL_P20
 | 
			
		||||
      default:
 | 
			
		||||
        tx_power_dbm = 0;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
#else
 | 
			
		||||
    // Unknown variant
 | 
			
		||||
    tx_power_dbm = 0;
 | 
			
		||||
#endif
 | 
			
		||||
    ESP_LOGCONFIG(TAG,
 | 
			
		||||
                  "BLE:\n"
 | 
			
		||||
                  "  MAC address: %s\n"
 | 
			
		||||
                  "  IO Capability: %s",
 | 
			
		||||
                  format_mac_address_pretty(mac_address).c_str(), io_capability_s);
 | 
			
		||||
                  "  IO Capability: %s\n"
 | 
			
		||||
                  "  TX Power: %d dBm",
 | 
			
		||||
                  format_mac_address_pretty(mac_address).c_str(), io_capability_s, tx_power_dbm);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <esp_bt.h>
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
#include <esp_gatts_api.h>
 | 
			
		||||
@@ -94,6 +95,7 @@ class BLEStatusEventHandler {
 | 
			
		||||
class ESP32BLE : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
 | 
			
		||||
  void set_tx_power(esp_power_level_t tx_power) { this->tx_power_ = tx_power; }
 | 
			
		||||
 | 
			
		||||
  void set_advertising_cycle_time(uint32_t advertising_cycle_time) {
 | 
			
		||||
    this->advertising_cycle_time_ = advertising_cycle_time;
 | 
			
		||||
@@ -172,6 +174,7 @@ class ESP32BLE : public Component {
 | 
			
		||||
  // 1-byte aligned members (grouped together to minimize padding)
 | 
			
		||||
  BLEComponentState state_{BLE_COMPONENT_STATE_OFF};  // 1 byte (uint8_t enum)
 | 
			
		||||
  bool enable_on_boot_{};                             // 1 byte
 | 
			
		||||
  esp_power_level_t tx_power_{ESP_PWR_LVL_P9};        // 1 byte (default: +9 dBm)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 | 
			
		||||
#include <esp_gap_ble_api.h>
 | 
			
		||||
#include <esp_gatt_defs.h>
 | 
			
		||||
#include <esp_gattc_api.h>
 | 
			
		||||
 | 
			
		||||
namespace esphome::esp32_ble_client {
 | 
			
		||||
 | 
			
		||||
@@ -111,43 +112,19 @@ void BLEClientBase::connect() {
 | 
			
		||||
           this->remote_addr_type_);
 | 
			
		||||
  this->paired_ = false;
 | 
			
		||||
 | 
			
		||||
  // Set preferred connection parameters before connecting
 | 
			
		||||
  // Use FAST for all V3 connections (better latency and reliability)
 | 
			
		||||
  // Use MEDIUM for V1/legacy connections (balanced performance)
 | 
			
		||||
  uint16_t min_interval, max_interval, timeout;
 | 
			
		||||
  const char *param_type;
 | 
			
		||||
 | 
			
		||||
  if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
 | 
			
		||||
      this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
    min_interval = FAST_MIN_CONN_INTERVAL;
 | 
			
		||||
    max_interval = FAST_MAX_CONN_INTERVAL;
 | 
			
		||||
    timeout = FAST_CONN_TIMEOUT;
 | 
			
		||||
    param_type = "fast";
 | 
			
		||||
  } else {
 | 
			
		||||
    min_interval = MEDIUM_MIN_CONN_INTERVAL;
 | 
			
		||||
    max_interval = MEDIUM_MAX_CONN_INTERVAL;
 | 
			
		||||
    timeout = MEDIUM_CONN_TIMEOUT;
 | 
			
		||||
    param_type = "medium";
 | 
			
		||||
  // Determine connection parameters based on connection type
 | 
			
		||||
  if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
			
		||||
    // V3 without cache needs fast params for service discovery
 | 
			
		||||
    this->set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT, "fast");
 | 
			
		||||
  } else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
    // V3 with cache can use medium params
 | 
			
		||||
    this->set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
 | 
			
		||||
  }
 | 
			
		||||
  // For V1/Legacy, don't set params - use ESP-IDF defaults
 | 
			
		||||
 | 
			
		||||
  auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
 | 
			
		||||
                                                      0,  // latency: 0
 | 
			
		||||
                                                      timeout);
 | 
			
		||||
  if (param_ret != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str(), param_ret);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->log_connection_params_(param_type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Now open the connection
 | 
			
		||||
  // Open the connection
 | 
			
		||||
  auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    this->log_gattc_warning_("esp_ble_gattc_open", ret);
 | 
			
		||||
    this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->set_state(espbt::ClientState::CONNECTING);
 | 
			
		||||
  }
 | 
			
		||||
  this->handle_connection_result_(ret);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
 | 
			
		||||
@@ -159,7 +136,7 @@ void BLEClientBase::disconnect() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
 | 
			
		||||
    ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_,
 | 
			
		||||
    ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    this->want_disconnect_ = true;
 | 
			
		||||
    return;
 | 
			
		||||
@@ -172,13 +149,11 @@ void BLEClientBase::unconditional_disconnect() {
 | 
			
		||||
  ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
           this->conn_id_);
 | 
			
		||||
  if (this->state_ == espbt::ClientState::DISCONNECTING) {
 | 
			
		||||
    ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    this->log_error_("Already disconnecting");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (this->conn_id_ == UNSET_CONN_ID) {
 | 
			
		||||
    ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_,
 | 
			
		||||
             this->address_str_.c_str());
 | 
			
		||||
    this->log_error_("conn id unset, cannot disconnect");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
 | 
			
		||||
@@ -234,17 +209,51 @@ void BLEClientBase::log_connection_params_(const char *param_type) {
 | 
			
		||||
  ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::restore_medium_conn_params_() {
 | 
			
		||||
  // Restore to medium connection parameters after initial connection phase
 | 
			
		||||
  // This balances performance with bandwidth usage for normal operation
 | 
			
		||||
void BLEClientBase::handle_connection_result_(esp_err_t ret) {
 | 
			
		||||
  if (ret) {
 | 
			
		||||
    this->log_gattc_warning_("esp_ble_gattc_open", ret);
 | 
			
		||||
    this->set_state(espbt::ClientState::IDLE);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->set_state(espbt::ClientState::CONNECTING);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::log_error_(const char *message) {
 | 
			
		||||
  ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::log_error_(const char *message, int code) {
 | 
			
		||||
  ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::log_warning_(const char *message) {
 | 
			
		||||
  ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
 | 
			
		||||
                                        uint16_t timeout, const char *param_type) {
 | 
			
		||||
  esp_ble_conn_update_params_t conn_params = {{0}};
 | 
			
		||||
  memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
 | 
			
		||||
  conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
 | 
			
		||||
  conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
 | 
			
		||||
  conn_params.latency = 0;
 | 
			
		||||
  conn_params.timeout = MEDIUM_CONN_TIMEOUT;
 | 
			
		||||
  this->log_connection_params_("medium");
 | 
			
		||||
  esp_ble_gap_update_conn_params(&conn_params);
 | 
			
		||||
  conn_params.min_int = min_interval;
 | 
			
		||||
  conn_params.max_int = max_interval;
 | 
			
		||||
  conn_params.latency = latency;
 | 
			
		||||
  conn_params.timeout = timeout;
 | 
			
		||||
  this->log_connection_params_(param_type);
 | 
			
		||||
  esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->log_gattc_warning_("esp_ble_gap_update_conn_params", err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BLEClientBase::set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
 | 
			
		||||
                                     const char *param_type) {
 | 
			
		||||
  // Set preferred connection parameters before connecting
 | 
			
		||||
  // These will be used when establishing the connection
 | 
			
		||||
  this->log_connection_params_(param_type);
 | 
			
		||||
  esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval, latency, timeout);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    this->log_gattc_warning_("esp_ble_gap_set_prefer_conn_params", err);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
 | 
			
		||||
@@ -264,8 +273,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
                 this->app_id);
 | 
			
		||||
        this->gattc_if_ = esp_gattc_if;
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->reg.app_id, param->reg.status);
 | 
			
		||||
        this->log_error_("gattc app registration failed status", param->reg.status);
 | 
			
		||||
        this->status_ = param->reg.status;
 | 
			
		||||
        this->mark_failed();
 | 
			
		||||
      }
 | 
			
		||||
@@ -277,11 +285,21 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      this->log_gattc_event_("OPEN");
 | 
			
		||||
      // conn_id was already set in ESP_GATTC_CONNECT_EVT
 | 
			
		||||
      this->service_count_ = 0;
 | 
			
		||||
 | 
			
		||||
      // ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
 | 
			
		||||
      // error, if the error occurred at the BTA/GATT layer. This can result in the event
 | 
			
		||||
      // arriving after we've already transitioned to IDLE state.
 | 
			
		||||
      if (this->state_ == espbt::ClientState::IDLE) {
 | 
			
		||||
        ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->open.status);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this->state_ != espbt::ClientState::CONNECTING) {
 | 
			
		||||
        // This should not happen but lets log it in case it does
 | 
			
		||||
        // because it means we have a bad assumption about how the
 | 
			
		||||
        // ESP BT stack works.
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while in %s state, status=%d", this->connection_index_,
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
 | 
			
		||||
      }
 | 
			
		||||
      if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
 | 
			
		||||
@@ -301,13 +319,14 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      this->set_state(espbt::ClientState::CONNECTED);
 | 
			
		||||
      ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
      if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
        // Restore to medium connection parameters for cached connections too
 | 
			
		||||
        this->restore_medium_conn_params_();
 | 
			
		||||
        // Cached connections already connected with medium parameters, no update needed
 | 
			
		||||
        // only set our state, subclients might have more stuff to do yet.
 | 
			
		||||
        this->state_ = espbt::ClientState::ESTABLISHED;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str());
 | 
			
		||||
      // For V3_WITHOUT_CACHE, we already set fast params before connecting
 | 
			
		||||
      // No need to update them again here
 | 
			
		||||
      this->log_event_("Searching for services");
 | 
			
		||||
      esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -332,8 +351,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      // Check if we were disconnected while waiting for service discovery
 | 
			
		||||
      if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
 | 
			
		||||
          this->state_ == espbt::ClientState::CONNECTED) {
 | 
			
		||||
        ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str());
 | 
			
		||||
        this->log_warning_("Remote closed during discovery");
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->disconnect.reason);
 | 
			
		||||
@@ -389,12 +407,11 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      if (this->conn_id_ != param->search_cmpl.conn_id)
 | 
			
		||||
        return false;
 | 
			
		||||
      this->log_gattc_event_("SEARCH_CMPL");
 | 
			
		||||
      // For V3 connections, restore to medium connection parameters after service discovery
 | 
			
		||||
      // For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
 | 
			
		||||
      // This balances performance with bandwidth usage after the critical discovery phase
 | 
			
		||||
      if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
 | 
			
		||||
          this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
        this->restore_medium_conn_params_();
 | 
			
		||||
      } else {
 | 
			
		||||
      if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
 | 
			
		||||
        this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
 | 
			
		||||
      } else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
 | 
			
		||||
#ifdef USE_ESP32_BLE_DEVICE
 | 
			
		||||
        for (auto &svc : this->services_) {
 | 
			
		||||
          ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
@@ -478,6 +495,11 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
 | 
			
		||||
      this->log_gattc_event_("UNREG_FOR_NOTIFY");
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    default:
 | 
			
		||||
      // ideally would check all other events for matching conn_id
 | 
			
		||||
      ESP_LOGD(TAG, "[%d] [%s] Event %d", this->connection_index_, this->address_str_.c_str(), event);
 | 
			
		||||
@@ -506,16 +528,14 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
 | 
			
		||||
        return;
 | 
			
		||||
      esp_bd_addr_t bd_addr;
 | 
			
		||||
      memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
 | 
			
		||||
      ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
      ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
               format_hex(bd_addr, 6).c_str());
 | 
			
		||||
      if (!param->ble_security.auth_cmpl.success) {
 | 
			
		||||
        ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
        this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
 | 
			
		||||
      } else {
 | 
			
		||||
        this->paired_ = true;
 | 
			
		||||
        ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
 | 
			
		||||
                 this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
 | 
			
		||||
                 param->ble_security.auth_cmpl.auth_mode);
 | 
			
		||||
        ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_.c_str(),
 | 
			
		||||
                 param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -133,10 +133,18 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
 | 
			
		||||
 | 
			
		||||
  void log_event_(const char *name);
 | 
			
		||||
  void log_gattc_event_(const char *name);
 | 
			
		||||
  void restore_medium_conn_params_();
 | 
			
		||||
  void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
 | 
			
		||||
                           const char *param_type);
 | 
			
		||||
  void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
 | 
			
		||||
                        const char *param_type);
 | 
			
		||||
  void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
 | 
			
		||||
  void log_gattc_warning_(const char *operation, esp_err_t err);
 | 
			
		||||
  void log_connection_params_(const char *param_type);
 | 
			
		||||
  void handle_connection_result_(esp_err_t ret);
 | 
			
		||||
  // Compact error logging helpers to reduce flash usage
 | 
			
		||||
  void log_error_(const char *message);
 | 
			
		||||
  void log_error_(const char *message, int code);
 | 
			
		||||
  void log_warning_(const char *message);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome::esp32_ble_client
 | 
			
		||||
 
 | 
			
		||||
@@ -80,14 +80,17 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>,
 | 
			
		||||
  ESPBTUUID uuid_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32_BLE_DEVICE
 | 
			
		||||
 | 
			
		||||
class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_BLE_DEVICE
 | 
			
		||||
  bool parse_device(const ESPBTDevice &device) override { return false; }
 | 
			
		||||
#endif
 | 
			
		||||
  void on_scan_end() override { this->trigger(); }
 | 
			
		||||
};
 | 
			
		||||
#endif  // USE_ESP32_BLE_DEVICE
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
 | 
			
		||||
 public:
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ extern "C" {
 | 
			
		||||
#include "preferences.h"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp8266 {
 | 
			
		||||
@@ -67,6 +67,8 @@ static uint32_t get_esp8266_flash_sector() {
 | 
			
		||||
}
 | 
			
		||||
static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
 | 
			
		||||
 | 
			
		||||
static inline size_t bytes_to_words(size_t bytes) { return (bytes + 3) / 4; }
 | 
			
		||||
 | 
			
		||||
template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) {
 | 
			
		||||
  uint32_t crc = type;
 | 
			
		||||
  while (first != last) {
 | 
			
		||||
@@ -123,41 +125,36 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
 | 
			
		||||
  size_t length_words = 0;
 | 
			
		||||
 | 
			
		||||
  bool save(const uint8_t *data, size_t len) override {
 | 
			
		||||
    if ((len + 3) / 4 != length_words) {
 | 
			
		||||
    if (bytes_to_words(len) != length_words) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    std::vector<uint32_t> buffer;
 | 
			
		||||
    buffer.resize(length_words + 1);
 | 
			
		||||
    memcpy(buffer.data(), data, len);
 | 
			
		||||
    buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
 | 
			
		||||
    size_t buffer_size = length_words + 1;
 | 
			
		||||
    std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());  // Note the () for zero-initialization
 | 
			
		||||
    memcpy(buffer.get(), data, len);
 | 
			
		||||
    buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type);
 | 
			
		||||
 | 
			
		||||
    if (in_flash) {
 | 
			
		||||
      return save_to_flash(offset, buffer.data(), buffer.size());
 | 
			
		||||
    } else {
 | 
			
		||||
      return save_to_rtc(offset, buffer.data(), buffer.size());
 | 
			
		||||
      return save_to_flash(offset, buffer.get(), buffer_size);
 | 
			
		||||
    }
 | 
			
		||||
    return save_to_rtc(offset, buffer.get(), buffer_size);
 | 
			
		||||
  }
 | 
			
		||||
  bool load(uint8_t *data, size_t len) override {
 | 
			
		||||
    if ((len + 3) / 4 != length_words) {
 | 
			
		||||
    if (bytes_to_words(len) != length_words) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    std::vector<uint32_t> buffer;
 | 
			
		||||
    buffer.resize(length_words + 1);
 | 
			
		||||
    bool ret;
 | 
			
		||||
    if (in_flash) {
 | 
			
		||||
      ret = load_from_flash(offset, buffer.data(), buffer.size());
 | 
			
		||||
    } else {
 | 
			
		||||
      ret = load_from_rtc(offset, buffer.data(), buffer.size());
 | 
			
		||||
    }
 | 
			
		||||
    size_t buffer_size = length_words + 1;
 | 
			
		||||
    std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());
 | 
			
		||||
    bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size)
 | 
			
		||||
                        : load_from_rtc(offset, buffer.get(), buffer_size);
 | 
			
		||||
    if (!ret)
 | 
			
		||||
      return false;
 | 
			
		||||
 | 
			
		||||
    uint32_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type);
 | 
			
		||||
    if (buffer[buffer.size() - 1] != crc) {
 | 
			
		||||
    uint32_t crc = calculate_crc(buffer.get(), buffer.get() + length_words, type);
 | 
			
		||||
    if (buffer[length_words] != crc) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    memcpy(data, buffer.data(), len);
 | 
			
		||||
    memcpy(data, buffer.get(), len);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -178,7 +175,7 @@ class ESP8266Preferences : public ESPPreferences {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
 | 
			
		||||
    uint32_t length_words = (length + 3) / 4;
 | 
			
		||||
    uint32_t length_words = bytes_to_words(length);
 | 
			
		||||
    if (in_flash) {
 | 
			
		||||
      uint32_t start = current_flash_offset;
 | 
			
		||||
      uint32_t end = start + length_words + 1;
 | 
			
		||||
 
 | 
			
		||||
@@ -148,7 +148,8 @@ void Fan::publish_state() {
 | 
			
		||||
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
 | 
			
		||||
optional<FanRestoreState> Fan::restore_state_() {
 | 
			
		||||
  FanRestoreState recovered{};
 | 
			
		||||
  this->rtc_ = global_preferences->make_preference<FanRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
 | 
			
		||||
  this->rtc_ =
 | 
			
		||||
      global_preferences->make_preference<FanRestoreState>(this->get_preference_hash() ^ RESTORE_STATE_VERSION);
 | 
			
		||||
  bool restored = this->rtc_.load(&recovered);
 | 
			
		||||
 | 
			
		||||
  switch (this->restore_mode_) {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,10 +58,10 @@ void GroveGasMultichannelV2Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2");
 | 
			
		||||
  LOG_I2C_DEVICE(this)
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this)
 | 
			
		||||
  LOG_SENSOR("  ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Ethanol", this->ethanol_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Carbon Monoxide", this->carbon_monoxide_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Ethanol", this->ethanol_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Carbon Monoxide", this->carbon_monoxide_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "TVOC", this->tvoc_sensor_);
 | 
			
		||||
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    switch (this->error_code_) {
 | 
			
		||||
 
 | 
			
		||||
@@ -351,7 +351,7 @@ ClimateTraits HaierClimateBase::traits() { return traits_; }
 | 
			
		||||
void HaierClimateBase::initialization() {
 | 
			
		||||
  constexpr uint32_t restore_settings_version = 0xA77D21EF;
 | 
			
		||||
  this->base_rtc_ =
 | 
			
		||||
      global_preferences->make_preference<HaierBaseSettings>(this->get_object_id_hash() ^ restore_settings_version);
 | 
			
		||||
      global_preferences->make_preference<HaierBaseSettings>(this->get_preference_hash() ^ restore_settings_version);
 | 
			
		||||
  HaierBaseSettings recovered;
 | 
			
		||||
  if (!this->base_rtc_.load(&recovered)) {
 | 
			
		||||
    recovered = {false, true};
 | 
			
		||||
 
 | 
			
		||||
@@ -516,7 +516,7 @@ void HonClimate::initialization() {
 | 
			
		||||
  HaierClimateBase::initialization();
 | 
			
		||||
  constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
 | 
			
		||||
  this->hon_rtc_ =
 | 
			
		||||
      global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
 | 
			
		||||
      global_preferences->make_preference<HonSettings>(this->get_preference_hash() ^ restore_settings_version);
 | 
			
		||||
  HonSettings recovered;
 | 
			
		||||
  if (this->hon_rtc_.load(&recovered)) {
 | 
			
		||||
    this->settings_ = recovered;
 | 
			
		||||
 
 | 
			
		||||
@@ -43,10 +43,10 @@ void HLW8012Component::dump_config() {
 | 
			
		||||
                "  Voltage Divider: %.1f",
 | 
			
		||||
                this->change_mode_every_, this->current_resistor_ * 1000.0f, this->voltage_divider_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this)
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage", this->voltage_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Current", this->current_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Power", this->power_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Energy", this->energy_sensor_)
 | 
			
		||||
  LOG_SENSOR("  ", "Voltage", this->voltage_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Current", this->current_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Power", this->power_sensor_);
 | 
			
		||||
  LOG_SENSOR("  ", "Energy", this->energy_sensor_);
 | 
			
		||||
}
 | 
			
		||||
float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
void HLW8012Component::update() {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,9 @@ static const char *const TAG = "hte501";
 | 
			
		||||
 | 
			
		||||
void HTE501Component::setup() {
 | 
			
		||||
  uint8_t address[] = {0x70, 0x29};
 | 
			
		||||
  this->write(address, 2, false);
 | 
			
		||||
  uint8_t identification[9];
 | 
			
		||||
  this->read(identification, 9);
 | 
			
		||||
  if (identification[8] != calc_crc8_(identification, 0, 7)) {
 | 
			
		||||
  this->write_read(address, sizeof address, identification, sizeof identification);
 | 
			
		||||
  if (identification[8] != crc8(identification, 8, 0xFF, 0x31, true)) {
 | 
			
		||||
    this->error_code_ = CRC_CHECK_FAILED;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
@@ -42,11 +41,12 @@ void HTE501Component::dump_config() {
 | 
			
		||||
float HTE501Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
void HTE501Component::update() {
 | 
			
		||||
  uint8_t address_1[] = {0x2C, 0x1B};
 | 
			
		||||
  this->write(address_1, 2, true);
 | 
			
		||||
  this->write(address_1, 2);
 | 
			
		||||
  this->set_timeout(50, [this]() {
 | 
			
		||||
    uint8_t i2c_response[6];
 | 
			
		||||
    this->read(i2c_response, 6);
 | 
			
		||||
    if (i2c_response[2] != calc_crc8_(i2c_response, 0, 1) && i2c_response[5] != calc_crc8_(i2c_response, 3, 4)) {
 | 
			
		||||
    if (i2c_response[2] != crc8(i2c_response, 2, 0xFF, 0x31, true) &&
 | 
			
		||||
        i2c_response[5] != crc8(i2c_response + 3, 2, 0xFF, 0x31, true)) {
 | 
			
		||||
      this->error_code_ = CRC_CHECK_FAILED;
 | 
			
		||||
      this->status_set_warning();
 | 
			
		||||
      return;
 | 
			
		||||
@@ -67,24 +67,5 @@ void HTE501Component::update() {
 | 
			
		||||
    this->status_clear_warning();
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned char HTE501Component::calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to) {
 | 
			
		||||
  unsigned char crc_val = 0xFF;
 | 
			
		||||
  unsigned char i = 0;
 | 
			
		||||
  unsigned char j = 0;
 | 
			
		||||
  for (i = from; i <= to; i++) {
 | 
			
		||||
    int cur_val = buf[i];
 | 
			
		||||
    for (j = 0; j < 8; j++) {
 | 
			
		||||
      if (((crc_val ^ cur_val) & 0x80) != 0)  // If MSBs are not equal
 | 
			
		||||
      {
 | 
			
		||||
        crc_val = ((crc_val << 1) ^ 0x31);
 | 
			
		||||
      } else {
 | 
			
		||||
        crc_val = (crc_val << 1);
 | 
			
		||||
      }
 | 
			
		||||
      cur_val = cur_val << 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return crc_val;
 | 
			
		||||
}
 | 
			
		||||
}  // namespace hte501
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace hte501 {
 | 
			
		||||
@@ -19,7 +19,6 @@ class HTE501Component : public PollingComponent, public i2c::I2CDevice {
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  unsigned char calc_crc8_(const unsigned char buf[], unsigned char from, unsigned char to);
 | 
			
		||||
  sensor::Sensor *temperature_sensor_;
 | 
			
		||||
  sensor::Sensor *humidity_sensor_;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
#include "http_request_host.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
#define USE_HTTP_REQUEST_HOST_H
 | 
			
		||||
#define CPPHTTPLIB_NO_EXCEPTIONS
 | 
			
		||||
#include "httplib.h"
 | 
			
		||||
#include "http_request_host.h"
 | 
			
		||||
 | 
			
		||||
#include <regex>
 | 
			
		||||
#include "esphome/components/network/util.h"
 | 
			
		||||
#include "esphome/components/watchdog/watchdog.h"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
 | 
			
		||||
#define CPPHTTPLIB_NO_EXCEPTIONS
 | 
			
		||||
#include "httplib.h"
 | 
			
		||||
#include "http_request.h"
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace http_request {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,10 @@
 | 
			
		||||
/**
 | 
			
		||||
 * NOTE: This is a copy of httplib.h from https://github.com/yhirose/cpp-httplib
 | 
			
		||||
 *
 | 
			
		||||
 * It has been modified only to add ifdefs for USE_HOST. While it contains many functions unused in ESPHome,
 | 
			
		||||
 * It has been modified to add ifdefs for USE_HOST. While it contains many functions unused in ESPHome,
 | 
			
		||||
 * it was considered preferable to use it with as few changes as possible, to facilitate future updates.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//  httplib.h
 | 
			
		||||
//
 | 
			
		||||
@@ -17,6 +15,11 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifdef USE_HOST
 | 
			
		||||
// Prevent this code being included in main.cpp
 | 
			
		||||
#ifdef USE_HTTP_REQUEST_HOST_H
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
 | 
			
		||||
#ifndef CPPHTTPLIB_HTTPLIB_H
 | 
			
		||||
#define CPPHTTPLIB_HTTPLIB_H
 | 
			
		||||
 | 
			
		||||
@@ -9687,5 +9690,6 @@ inline SSL_CTX *Client::ssl_context() const {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#endif  // CPPHTTPLIB_HTTPLIB_H
 | 
			
		||||
#endif  // USE_HTTP_REQUEST_HOST_H
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import logging
 | 
			
		||||
 | 
			
		||||
from esphome import pins
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.components import esp32
 | 
			
		||||
from esphome.config_helpers import filter_source_files_from_platform
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
@@ -14,8 +13,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_SCL,
 | 
			
		||||
    CONF_SDA,
 | 
			
		||||
    CONF_TIMEOUT,
 | 
			
		||||
    KEY_CORE,
 | 
			
		||||
    KEY_FRAMEWORK_VERSION,
 | 
			
		||||
    PLATFORM_ESP32,
 | 
			
		||||
    PLATFORM_ESP8266,
 | 
			
		||||
    PLATFORM_RP2040,
 | 
			
		||||
@@ -48,28 +45,8 @@ def _bus_declare_type(value):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_config(config):
 | 
			
		||||
    if (
 | 
			
		||||
        config[CONF_SCAN]
 | 
			
		||||
        and CORE.is_esp32
 | 
			
		||||
        and CORE.using_esp_idf
 | 
			
		||||
        and esp32.get_esp32_variant()
 | 
			
		||||
        in [
 | 
			
		||||
            esp32.const.VARIANT_ESP32C5,
 | 
			
		||||
            esp32.const.VARIANT_ESP32C6,
 | 
			
		||||
            esp32.const.VARIANT_ESP32P4,
 | 
			
		||||
        ]
 | 
			
		||||
    ):
 | 
			
		||||
        version: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
 | 
			
		||||
        if version.major == 5 and (
 | 
			
		||||
            (version.minor == 3 and version.patch <= 3)
 | 
			
		||||
            or (version.minor == 4 and version.patch <= 1)
 | 
			
		||||
        ):
 | 
			
		||||
            LOGGER.warning(
 | 
			
		||||
                "There is a bug in esp-idf version %s that breaks I2C scan, I2C scan "
 | 
			
		||||
                "has been disabled, see https://github.com/esphome/issues/issues/7128",
 | 
			
		||||
                str(version),
 | 
			
		||||
            )
 | 
			
		||||
            config[CONF_SCAN] = False
 | 
			
		||||
    if CORE.using_esp_idf:
 | 
			
		||||
        return cv.require_framework_version(esp_idf=cv.Version(5, 4, 2))(config)
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
#include "i2c.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
@@ -7,38 +9,48 @@ namespace i2c {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "i2c";
 | 
			
		||||
 | 
			
		||||
ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop) {
 | 
			
		||||
  ErrorCode err = this->write(&a_register, 1, stop);
 | 
			
		||||
  if (err != ERROR_OK)
 | 
			
		||||
    return err;
 | 
			
		||||
  return bus_->read(address_, data, len);
 | 
			
		||||
void I2CBus::i2c_scan_() {
 | 
			
		||||
  // suppress logs from the IDF I2C library during the scan
 | 
			
		||||
#if defined(USE_ESP32) && defined(USE_LOGGER)
 | 
			
		||||
  auto previous = esp_log_level_get("*");
 | 
			
		||||
  esp_log_level_set("*", ESP_LOG_NONE);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  for (uint8_t address = 8; address != 120; address++) {
 | 
			
		||||
    auto err = write_readv(address, nullptr, 0, nullptr, 0);
 | 
			
		||||
    if (err == ERROR_OK) {
 | 
			
		||||
      scan_results_.emplace_back(address, true);
 | 
			
		||||
    } else if (err == ERROR_UNKNOWN) {
 | 
			
		||||
      scan_results_.emplace_back(address, false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#if defined(USE_ESP32) && defined(USE_LOGGER)
 | 
			
		||||
  esp_log_level_set("*", previous);
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop) {
 | 
			
		||||
ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len) {
 | 
			
		||||
  return bus_->write_readv(this->address_, &a_register, 1, data, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t len) {
 | 
			
		||||
  a_register = convert_big_endian(a_register);
 | 
			
		||||
  ErrorCode const err = this->write(reinterpret_cast<const uint8_t *>(&a_register), 2, stop);
 | 
			
		||||
  if (err != ERROR_OK)
 | 
			
		||||
    return err;
 | 
			
		||||
  return bus_->read(address_, data, len);
 | 
			
		||||
  return bus_->write_readv(this->address_, reinterpret_cast<const uint8_t *>(&a_register), 2, data, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop) {
 | 
			
		||||
  WriteBuffer buffers[2];
 | 
			
		||||
  buffers[0].data = &a_register;
 | 
			
		||||
  buffers[0].len = 1;
 | 
			
		||||
  buffers[1].data = data;
 | 
			
		||||
  buffers[1].len = len;
 | 
			
		||||
  return bus_->writev(address_, buffers, 2, stop);
 | 
			
		||||
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len) const {
 | 
			
		||||
  std::vector<uint8_t> v{};
 | 
			
		||||
  v.push_back(a_register);
 | 
			
		||||
  v.insert(v.end(), data, data + len);
 | 
			
		||||
  return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop) {
 | 
			
		||||
  a_register = convert_big_endian(a_register);
 | 
			
		||||
  WriteBuffer buffers[2];
 | 
			
		||||
  buffers[0].data = reinterpret_cast<const uint8_t *>(&a_register);
 | 
			
		||||
  buffers[0].len = 2;
 | 
			
		||||
  buffers[1].data = data;
 | 
			
		||||
  buffers[1].len = len;
 | 
			
		||||
  return bus_->writev(address_, buffers, 2, stop);
 | 
			
		||||
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const {
 | 
			
		||||
  std::vector<uint8_t> v(len + 2);
 | 
			
		||||
  v.push_back(a_register >> 8);
 | 
			
		||||
  v.push_back(a_register);
 | 
			
		||||
  v.insert(v.end(), data, data + len);
 | 
			
		||||
  return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
 | 
			
		||||
@@ -49,7 +61,7 @@ bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) {
 | 
			
		||||
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) const {
 | 
			
		||||
  // we have to copy in order to be able to change byte order
 | 
			
		||||
  std::unique_ptr<uint16_t[]> temp{new uint16_t[len]};
 | 
			
		||||
  for (size_t i = 0; i < len; i++)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "i2c_bus.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/optional.h"
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/optional.h"
 | 
			
		||||
#include "i2c_bus.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2c {
 | 
			
		||||
@@ -161,51 +161,53 @@ class I2CDevice {
 | 
			
		||||
  /// @param data pointer to an array to store the bytes
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to read
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); }
 | 
			
		||||
  ErrorCode read(uint8_t *data, size_t len) const { return bus_->write_readv(this->address_, nullptr, 0, data, len); }
 | 
			
		||||
 | 
			
		||||
  /// @brief reads an array of bytes from a specific register in the I²C device
 | 
			
		||||
  /// @param a_register an 8 bits internal address of the I²C register to read from
 | 
			
		||||
  /// @param data pointer to an array to store the bytes
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to read
 | 
			
		||||
  /// @param stop (true/false): True will send a stop message, releasing the bus after
 | 
			
		||||
  /// transmission. False will send a restart, keeping the connection active.
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true);
 | 
			
		||||
  ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len);
 | 
			
		||||
 | 
			
		||||
  /// @brief reads an array of bytes from a specific register in the I²C device
 | 
			
		||||
  /// @param a_register the 16 bits internal address of the I²C register to read from
 | 
			
		||||
  /// @param data pointer to an array of bytes to store the information
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to read
 | 
			
		||||
  /// @param stop (true/false): True will send a stop message, releasing the bus after
 | 
			
		||||
  /// transmission. False will send a restart, keeping the connection active.
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop = true);
 | 
			
		||||
  ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len);
 | 
			
		||||
 | 
			
		||||
  /// @brief writes an array of bytes to a device using an I2CBus
 | 
			
		||||
  /// @param data pointer to an array that contains the bytes to send
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to write
 | 
			
		||||
  /// @param stop (true/false): True will send a stop message, releasing the bus after
 | 
			
		||||
  /// transmission. False will send a restart, keeping the connection active.
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  ErrorCode write(const uint8_t *data, size_t len, bool stop = true) { return bus_->write(address_, data, len, stop); }
 | 
			
		||||
  ErrorCode write(const uint8_t *data, size_t len) const {
 | 
			
		||||
    return bus_->write_readv(this->address_, data, len, nullptr, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// @brief writes an array of bytes to a device, then reads an array, as a single transaction
 | 
			
		||||
  /// @param write_data pointer to an array that contains the bytes to send
 | 
			
		||||
  /// @param write_len length of the buffer = number of bytes to write
 | 
			
		||||
  /// @param read_data pointer to an array to store the bytes read
 | 
			
		||||
  /// @param read_len length of the buffer = number of bytes to read
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  ErrorCode write_read(const uint8_t *write_data, size_t write_len, uint8_t *read_data, size_t read_len) const {
 | 
			
		||||
    return bus_->write_readv(this->address_, write_data, write_len, read_data, read_len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// @brief writes an array of bytes to a specific register in the I²C device
 | 
			
		||||
  /// @param a_register the internal address of the register to read from
 | 
			
		||||
  /// @param data pointer to an array to store the bytes
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to read
 | 
			
		||||
  /// @param stop (true/false): True will send a stop message, releasing the bus after
 | 
			
		||||
  /// transmission. False will send a restart, keeping the connection active.
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true);
 | 
			
		||||
  ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) const;
 | 
			
		||||
 | 
			
		||||
  /// @brief write an array of bytes to a specific register in the I²C device
 | 
			
		||||
  /// @param a_register the 16 bits internal address of the register to read from
 | 
			
		||||
  /// @param data pointer to an array to store the bytes
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to read
 | 
			
		||||
  /// @param stop (true/false): True will send a stop message, releasing the bus after
 | 
			
		||||
  /// transmission. False will send a restart, keeping the connection active.
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop = true);
 | 
			
		||||
  ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len) const;
 | 
			
		||||
 | 
			
		||||
  ///
 | 
			
		||||
  /// Compat APIs
 | 
			
		||||
@@ -217,7 +219,7 @@ class I2CDevice {
 | 
			
		||||
    return read_register(a_register, data, len) == ERROR_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool read_bytes_raw(uint8_t *data, uint8_t len) { return read(data, len) == ERROR_OK; }
 | 
			
		||||
  bool read_bytes_raw(uint8_t *data, uint8_t len) const { return read(data, len) == ERROR_OK; }
 | 
			
		||||
 | 
			
		||||
  template<size_t N> optional<std::array<uint8_t, N>> read_bytes(uint8_t a_register) {
 | 
			
		||||
    std::array<uint8_t, N> res;
 | 
			
		||||
@@ -236,9 +238,7 @@ class I2CDevice {
 | 
			
		||||
 | 
			
		||||
  bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len);
 | 
			
		||||
 | 
			
		||||
  bool read_byte(uint8_t a_register, uint8_t *data, bool stop = true) {
 | 
			
		||||
    return read_register(a_register, data, 1, stop) == ERROR_OK;
 | 
			
		||||
  }
 | 
			
		||||
  bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; }
 | 
			
		||||
 | 
			
		||||
  optional<uint8_t> read_byte(uint8_t a_register) {
 | 
			
		||||
    uint8_t data;
 | 
			
		||||
@@ -249,11 +249,11 @@ class I2CDevice {
 | 
			
		||||
 | 
			
		||||
  bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
 | 
			
		||||
 | 
			
		||||
  bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) {
 | 
			
		||||
    return write_register(a_register, data, len, stop) == ERROR_OK;
 | 
			
		||||
  bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const {
 | 
			
		||||
    return write_register(a_register, data, len) == ERROR_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
 | 
			
		||||
  bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) const {
 | 
			
		||||
    return write_bytes(a_register, data.data(), data.size());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -261,13 +261,42 @@ class I2CDevice {
 | 
			
		||||
    return write_bytes(a_register, data.data(), data.size());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len);
 | 
			
		||||
  bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) const;
 | 
			
		||||
 | 
			
		||||
  bool write_byte(uint8_t a_register, uint8_t data, bool stop = true) {
 | 
			
		||||
    return write_bytes(a_register, &data, 1, stop);
 | 
			
		||||
  bool write_byte(uint8_t a_register, uint8_t data) const { return write_bytes(a_register, &data, 1); }
 | 
			
		||||
 | 
			
		||||
  bool write_byte_16(uint8_t a_register, uint16_t data) const { return write_bytes_16(a_register, &data, 1); }
 | 
			
		||||
 | 
			
		||||
  // Deprecated functions
 | 
			
		||||
 | 
			
		||||
  ESPDEPRECATED("The stop argument is no longer used. This will be removed from ESPHome 2026.3.0", "2025.9.0")
 | 
			
		||||
  ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop) {
 | 
			
		||||
    return this->read_register(a_register, data, len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); }
 | 
			
		||||
  ESPDEPRECATED("The stop argument is no longer used. This will be removed from ESPHome 2026.3.0", "2025.9.0")
 | 
			
		||||
  ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop) {
 | 
			
		||||
    return this->read_register16(a_register, data, len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
 | 
			
		||||
                "removed from ESPHome 2026.3.0",
 | 
			
		||||
                "2025.9.0")
 | 
			
		||||
  ErrorCode write(const uint8_t *data, size_t len, bool stop) const { return this->write(data, len); }
 | 
			
		||||
 | 
			
		||||
  ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
 | 
			
		||||
                "removed from ESPHome 2026.3.0",
 | 
			
		||||
                "2025.9.0")
 | 
			
		||||
  ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop) const {
 | 
			
		||||
    return this->write_register(a_register, data, len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
 | 
			
		||||
                "removed from ESPHome 2026.3.0",
 | 
			
		||||
                "2025.9.0")
 | 
			
		||||
  ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop) const {
 | 
			
		||||
    return this->write_register16(a_register, data, len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t address_{0x00};  ///< store the address of the device on the bus
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2c {
 | 
			
		||||
 | 
			
		||||
@@ -39,71 +42,66 @@ struct WriteBuffer {
 | 
			
		||||
/// note https://www.nxp.com/docs/en/application-note/AN10216.pdf
 | 
			
		||||
class I2CBus {
 | 
			
		||||
 public:
 | 
			
		||||
  /// @brief Creates a ReadBuffer and calls the virtual readv() method to read bytes into this buffer
 | 
			
		||||
  /// @param address address of the I²C component on the i2c bus
 | 
			
		||||
  /// @param buffer pointer to an array of bytes that will be used to store the data received
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to read
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  virtual ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) {
 | 
			
		||||
    ReadBuffer buf;
 | 
			
		||||
    buf.data = buffer;
 | 
			
		||||
    buf.len = len;
 | 
			
		||||
    return readv(address, &buf, 1);
 | 
			
		||||
  }
 | 
			
		||||
  virtual ~I2CBus() = default;
 | 
			
		||||
 | 
			
		||||
  /// @brief This virtual method reads bytes from an I2CBus into an array of ReadBuffer.
 | 
			
		||||
  /// @param address address of the I²C component on the i2c bus
 | 
			
		||||
  /// @param buffers pointer to an array of ReadBuffer
 | 
			
		||||
  /// @param count number of ReadBuffer to read
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  /// @details This is a pure virtual method that must be implemented in a subclass.
 | 
			
		||||
  virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t count) = 0;
 | 
			
		||||
 | 
			
		||||
  virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) {
 | 
			
		||||
    return write(address, buffer, len, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// @brief Creates a WriteBuffer and calls the writev() method to send the bytes from this buffer
 | 
			
		||||
  /// @param address address of the I²C component on the i2c bus
 | 
			
		||||
  /// @param buffer pointer to an array of bytes that contains the data to be sent
 | 
			
		||||
  /// @param len length of the buffer = number of bytes to write
 | 
			
		||||
  /// @param stop true or false: True will send a stop message, releasing the bus after
 | 
			
		||||
  /// transmission. False will send a restart, keeping the connection active.
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) {
 | 
			
		||||
    WriteBuffer buf;
 | 
			
		||||
    buf.data = buffer;
 | 
			
		||||
    buf.len = len;
 | 
			
		||||
    return writev(address, &buf, 1, stop);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
 | 
			
		||||
    return writev(address, buffers, cnt, true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// @brief This virtual method writes bytes to an I2CBus from an array of WriteBuffer.
 | 
			
		||||
  /// @param address address of the I²C component on the i2c bus
 | 
			
		||||
  /// @param buffers pointer to an array of WriteBuffer
 | 
			
		||||
  /// @param count number of WriteBuffer to write
 | 
			
		||||
  /// @param stop true or false: True will send a stop message, releasing the bus after
 | 
			
		||||
  /// @brief This virtual method writes bytes to an I2CBus from an array,
 | 
			
		||||
  /// then reads bytes into an array of ReadBuffer.
 | 
			
		||||
  /// @param address address of the I²C device on the i2c bus
 | 
			
		||||
  /// @param write_buffer pointer to data
 | 
			
		||||
  /// @param write_count number of bytes to write
 | 
			
		||||
  /// @param read_buffer pointer to an array to receive data
 | 
			
		||||
  /// @param read_count number of bytes to read
 | 
			
		||||
  /// transmission. False will send a restart, keeping the connection active.
 | 
			
		||||
  /// @return an i2c::ErrorCode
 | 
			
		||||
  /// @details This is a pure virtual method that must be implemented in the subclass.
 | 
			
		||||
  virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t count, bool stop) = 0;
 | 
			
		||||
  virtual ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
 | 
			
		||||
                                size_t read_count) = 0;
 | 
			
		||||
 | 
			
		||||
  // Legacy functions for compatibility
 | 
			
		||||
 | 
			
		||||
  ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) {
 | 
			
		||||
    return this->write_readv(address, nullptr, 0, buffer, len);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop = true) {
 | 
			
		||||
    return this->write_readv(address, buffer, len, nullptr, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.",
 | 
			
		||||
                "2025.9.0")
 | 
			
		||||
  ErrorCode readv(uint8_t address, ReadBuffer *read_buffers, size_t count) {
 | 
			
		||||
    size_t total_len = 0;
 | 
			
		||||
    for (size_t i = 0; i != count; i++) {
 | 
			
		||||
      total_len += read_buffers[i].len;
 | 
			
		||||
    }
 | 
			
		||||
    std::vector<uint8_t> buffer(total_len);
 | 
			
		||||
    auto err = this->write_readv(address, nullptr, 0, buffer.data(), total_len);
 | 
			
		||||
    if (err != ERROR_OK)
 | 
			
		||||
      return err;
 | 
			
		||||
    size_t pos = 0;
 | 
			
		||||
    for (size_t i = 0; i != count; i++) {
 | 
			
		||||
      if (read_buffers[i].len != 0) {
 | 
			
		||||
        std::memcpy(read_buffers[i].data, buffer.data() + pos, read_buffers[i].len);
 | 
			
		||||
        pos += read_buffers[i].len;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return ERROR_OK;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.",
 | 
			
		||||
                "2025.9.0")
 | 
			
		||||
  ErrorCode writev(uint8_t address, const WriteBuffer *write_buffers, size_t count, bool stop = true) {
 | 
			
		||||
    std::vector<uint8_t> buffer{};
 | 
			
		||||
    for (size_t i = 0; i != count; i++) {
 | 
			
		||||
      buffer.insert(buffer.end(), write_buffers[i].data, write_buffers[i].data + write_buffers[i].len);
 | 
			
		||||
    }
 | 
			
		||||
    return this->write_readv(address, buffer.data(), buffer.size(), nullptr, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// @brief Scans the I2C bus for devices. Devices presence is kept in an array of std::pair
 | 
			
		||||
  /// that contains the address and the corresponding bool presence flag.
 | 
			
		||||
  virtual void i2c_scan() {
 | 
			
		||||
    for (uint8_t address = 8; address < 120; address++) {
 | 
			
		||||
      auto err = writev(address, nullptr, 0);
 | 
			
		||||
      if (err == ERROR_OK) {
 | 
			
		||||
        scan_results_.emplace_back(address, true);
 | 
			
		||||
      } else if (err == ERROR_UNKNOWN) {
 | 
			
		||||
        scan_results_.emplace_back(address, false);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  void i2c_scan_();
 | 
			
		||||
  std::vector<std::pair<uint8_t, bool>> scan_results_;  ///< array containing scan results
 | 
			
		||||
  bool scan_{false};                                    ///< Should we scan ? Can be set in the yaml
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ void ArduinoI2CBus::setup() {
 | 
			
		||||
  this->initialized_ = true;
 | 
			
		||||
  if (this->scan_) {
 | 
			
		||||
    ESP_LOGV(TAG, "Scanning bus for active devices");
 | 
			
		||||
    this->i2c_scan();
 | 
			
		||||
    this->i2c_scan_();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -111,88 +111,37 @@ void ArduinoI2CBus::dump_config() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
 | 
			
		||||
ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
 | 
			
		||||
                                     uint8_t *read_buffer, size_t read_count) {
 | 
			
		||||
#if defined(USE_ESP8266)
 | 
			
		||||
  this->set_pins_and_clock_();  // reconfigure Wire global state in case there are multiple instances
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // logging is only enabled with vv level, if warnings are shown the caller
 | 
			
		||||
  // should log them
 | 
			
		||||
  if (!initialized_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "i2c bus not initialized!");
 | 
			
		||||
    return ERROR_NOT_INITIALIZED;
 | 
			
		||||
  }
 | 
			
		||||
  size_t to_request = 0;
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++)
 | 
			
		||||
    to_request += buffers[i].len;
 | 
			
		||||
  size_t ret = wire_->requestFrom(address, to_request, true);
 | 
			
		||||
  if (ret != to_request) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret);
 | 
			
		||||
    return ERROR_TIMEOUT;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    for (size_t j = 0; j < buf.len; j++)
 | 
			
		||||
      buf.data[j] = wire_->read();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
  char debug_buf[4];
 | 
			
		||||
  std::string debug_hex;
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    for (size_t j = 0; j < buf.len; j++) {
 | 
			
		||||
      snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
 | 
			
		||||
      debug_hex += debug_buf;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  return ERROR_OK;
 | 
			
		||||
}
 | 
			
		||||
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
 | 
			
		||||
#if defined(USE_ESP8266)
 | 
			
		||||
  this->set_pins_and_clock_();  // reconfigure Wire global state in case there are multiple instances
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  // logging is only enabled with vv level, if warnings are shown the caller
 | 
			
		||||
  // should log them
 | 
			
		||||
  if (!initialized_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "i2c bus not initialized!");
 | 
			
		||||
    ESP_LOGD(TAG, "i2c bus not initialized!");
 | 
			
		||||
    return ERROR_NOT_INITIALIZED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
  char debug_buf[4];
 | 
			
		||||
  std::string debug_hex;
 | 
			
		||||
  ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    for (size_t j = 0; j < buf.len; j++) {
 | 
			
		||||
      snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
 | 
			
		||||
      debug_hex += debug_buf;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  wire_->beginTransmission(address);
 | 
			
		||||
  size_t written = 0;
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    if (buf.len == 0)
 | 
			
		||||
      continue;
 | 
			
		||||
    size_t ret = wire_->write(buf.data, buf.len);
 | 
			
		||||
    written += ret;
 | 
			
		||||
    if (ret != buf.len) {
 | 
			
		||||
      ESP_LOGVV(TAG, "TX failed at %u", written);
 | 
			
		||||
  uint8_t status = 0;
 | 
			
		||||
  if (write_count != 0 || read_count == 0) {
 | 
			
		||||
    wire_->beginTransmission(address);
 | 
			
		||||
    size_t ret = wire_->write(write_buffer, write_count);
 | 
			
		||||
    if (ret != write_count) {
 | 
			
		||||
      ESP_LOGV(TAG, "TX failed");
 | 
			
		||||
      return ERROR_UNKNOWN;
 | 
			
		||||
    }
 | 
			
		||||
    status = wire_->endTransmission(read_count == 0);
 | 
			
		||||
  }
 | 
			
		||||
  if (status == 0 && read_count != 0) {
 | 
			
		||||
    size_t ret2 = wire_->requestFrom(address, read_count, true);
 | 
			
		||||
    if (ret2 != read_count) {
 | 
			
		||||
      ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", read_count, address, ret2);
 | 
			
		||||
      return ERROR_TIMEOUT;
 | 
			
		||||
    }
 | 
			
		||||
    for (size_t j = 0; j != read_count; j++)
 | 
			
		||||
      read_buffer[j] = wire_->read();
 | 
			
		||||
  }
 | 
			
		||||
  uint8_t status = wire_->endTransmission(stop);
 | 
			
		||||
  switch (status) {
 | 
			
		||||
    case 0:
 | 
			
		||||
      return ERROR_OK;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,8 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
 | 
			
		||||
  ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
 | 
			
		||||
  ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
 | 
			
		||||
                        size_t read_count) override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
			
		||||
 | 
			
		||||
  void set_scan(bool scan) { scan_ = scan; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include "i2c_bus_esp_idf.h"
 | 
			
		||||
 | 
			
		||||
#include <driver/gpio.h>
 | 
			
		||||
#include <cinttypes>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
@@ -9,10 +10,6 @@
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0)
 | 
			
		||||
#define SOC_HP_I2C_NUM SOC_I2C_NUM
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2c {
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +31,6 @@ void IDFI2CBus::setup() {
 | 
			
		||||
 | 
			
		||||
  this->recover_();
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
 | 
			
		||||
  next_port = (i2c_port_t) (next_port + 1);
 | 
			
		||||
 | 
			
		||||
  i2c_master_bus_config_t bus_conf{};
 | 
			
		||||
@@ -77,56 +73,8 @@ void IDFI2CBus::setup() {
 | 
			
		||||
 | 
			
		||||
  if (this->scan_) {
 | 
			
		||||
    ESP_LOGV(TAG, "Scanning for devices");
 | 
			
		||||
    this->i2c_scan();
 | 
			
		||||
    this->i2c_scan_();
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
#if SOC_HP_I2C_NUM > 1
 | 
			
		||||
  next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
 | 
			
		||||
#else
 | 
			
		||||
  next_port = I2C_NUM_MAX;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  i2c_config_t conf{};
 | 
			
		||||
  memset(&conf, 0, sizeof(conf));
 | 
			
		||||
  conf.mode = I2C_MODE_MASTER;
 | 
			
		||||
  conf.sda_io_num = sda_pin_;
 | 
			
		||||
  conf.sda_pullup_en = sda_pullup_enabled_;
 | 
			
		||||
  conf.scl_io_num = scl_pin_;
 | 
			
		||||
  conf.scl_pullup_en = scl_pullup_enabled_;
 | 
			
		||||
  conf.master.clk_speed = frequency_;
 | 
			
		||||
#ifdef USE_ESP32_VARIANT_ESP32S2
 | 
			
		||||
  // workaround for https://github.com/esphome/issues/issues/6718
 | 
			
		||||
  conf.clk_flags = I2C_SCLK_SRC_FLAG_AWARE_DFS;
 | 
			
		||||
#endif
 | 
			
		||||
  esp_err_t err = i2c_param_config(port_, &conf);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "i2c_param_config failed: %s", esp_err_to_name(err));
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (timeout_ > 0) {
 | 
			
		||||
    err = i2c_set_timeout(port_, timeout_ * 80);  // unit: APB 80MHz clock cycle
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "i2c_set_timeout failed: %s", esp_err_to_name(err));
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
    } else {
 | 
			
		||||
      ESP_LOGV(TAG, "i2c_timeout set to %" PRIu32 " ticks (%" PRIu32 " us)", timeout_ * 80, timeout_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, 0);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  initialized_ = true;
 | 
			
		||||
  if (this->scan_) {
 | 
			
		||||
    ESP_LOGV(TAG, "Scanning bus for active devices");
 | 
			
		||||
    this->i2c_scan();
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFI2CBus::dump_config() {
 | 
			
		||||
@@ -166,267 +114,73 @@ void IDFI2CBus::dump_config() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
 | 
			
		||||
void IDFI2CBus::i2c_scan() {
 | 
			
		||||
  for (uint8_t address = 8; address < 120; address++) {
 | 
			
		||||
    auto err = i2c_master_probe(this->bus_, address, 20);
 | 
			
		||||
    if (err == ESP_OK) {
 | 
			
		||||
      this->scan_results_.emplace_back(address, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
 | 
			
		||||
  // logging is only enabled with vv level, if warnings are shown the caller
 | 
			
		||||
ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
 | 
			
		||||
                                 size_t read_count) {
 | 
			
		||||
  // logging is only enabled with v level, if warnings are shown the caller
 | 
			
		||||
  // should log them
 | 
			
		||||
  if (!initialized_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "i2c bus not initialized!");
 | 
			
		||||
    ESP_LOGW(TAG, "i2c bus not initialized!");
 | 
			
		||||
    return ERROR_NOT_INITIALIZED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
 | 
			
		||||
  i2c_operation_job_t jobs[cnt + 4];
 | 
			
		||||
  uint8_t read = (address << 1) | I2C_MASTER_READ;
 | 
			
		||||
  size_t last = 0, num = 0;
 | 
			
		||||
 | 
			
		||||
  jobs[num].command = I2C_MASTER_CMD_START;
 | 
			
		||||
  num++;
 | 
			
		||||
 | 
			
		||||
  jobs[num].command = I2C_MASTER_CMD_WRITE;
 | 
			
		||||
  jobs[num].write.ack_check = true;
 | 
			
		||||
  jobs[num].write.data = &read;
 | 
			
		||||
  jobs[num].write.total_bytes = 1;
 | 
			
		||||
  num++;
 | 
			
		||||
 | 
			
		||||
  // find the last valid index
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    if (buf.len == 0) {
 | 
			
		||||
      continue;
 | 
			
		||||
  i2c_operation_job_t jobs[8]{};
 | 
			
		||||
  size_t num_jobs = 0;
 | 
			
		||||
  uint8_t write_addr = (address << 1) | I2C_MASTER_WRITE;
 | 
			
		||||
  uint8_t read_addr = (address << 1) | I2C_MASTER_READ;
 | 
			
		||||
  ESP_LOGV(TAG, "Writing %zu bytes, reading %zu bytes", write_count, read_count);
 | 
			
		||||
  if (read_count == 0 && write_count == 0) {
 | 
			
		||||
    // basically just a bus probe. Send a start, address and stop
 | 
			
		||||
    ESP_LOGV(TAG, "0x%02X BUS PROBE", address);
 | 
			
		||||
    jobs[num_jobs++].command = I2C_MASTER_CMD_START;
 | 
			
		||||
    jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
 | 
			
		||||
    jobs[num_jobs].write.ack_check = true;
 | 
			
		||||
    jobs[num_jobs].write.data = &write_addr;
 | 
			
		||||
    jobs[num_jobs++].write.total_bytes = 1;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (write_count != 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
 | 
			
		||||
      jobs[num_jobs++].command = I2C_MASTER_CMD_START;
 | 
			
		||||
      jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
 | 
			
		||||
      jobs[num_jobs].write.ack_check = true;
 | 
			
		||||
      jobs[num_jobs].write.data = &write_addr;
 | 
			
		||||
      jobs[num_jobs++].write.total_bytes = 1;
 | 
			
		||||
      jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
 | 
			
		||||
      jobs[num_jobs].write.ack_check = true;
 | 
			
		||||
      jobs[num_jobs].write.data = (uint8_t *) write_buffer;
 | 
			
		||||
      jobs[num_jobs++].write.total_bytes = write_count;
 | 
			
		||||
    }
 | 
			
		||||
    last = i;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    if (buf.len == 0) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    if (i == last) {
 | 
			
		||||
      // the last byte read before stop should always be a nack,
 | 
			
		||||
      // split the last read if len is larger than 1
 | 
			
		||||
      if (buf.len > 1) {
 | 
			
		||||
        jobs[num].command = I2C_MASTER_CMD_READ;
 | 
			
		||||
        jobs[num].read.ack_value = I2C_ACK_VAL;
 | 
			
		||||
        jobs[num].read.data = (uint8_t *) buf.data;
 | 
			
		||||
        jobs[num].read.total_bytes = buf.len - 1;
 | 
			
		||||
        num++;
 | 
			
		||||
    if (read_count != 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "0x%02X RX bytes %zu", address, read_count);
 | 
			
		||||
      jobs[num_jobs++].command = I2C_MASTER_CMD_START;
 | 
			
		||||
      jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
 | 
			
		||||
      jobs[num_jobs].write.ack_check = true;
 | 
			
		||||
      jobs[num_jobs].write.data = &read_addr;
 | 
			
		||||
      jobs[num_jobs++].write.total_bytes = 1;
 | 
			
		||||
      if (read_count > 1) {
 | 
			
		||||
        jobs[num_jobs].command = I2C_MASTER_CMD_READ;
 | 
			
		||||
        jobs[num_jobs].read.ack_value = I2C_ACK_VAL;
 | 
			
		||||
        jobs[num_jobs].read.data = read_buffer;
 | 
			
		||||
        jobs[num_jobs++].read.total_bytes = read_count - 1;
 | 
			
		||||
      }
 | 
			
		||||
      jobs[num].command = I2C_MASTER_CMD_READ;
 | 
			
		||||
      jobs[num].read.ack_value = I2C_NACK_VAL;
 | 
			
		||||
      jobs[num].read.data = (uint8_t *) buf.data + buf.len - 1;
 | 
			
		||||
      jobs[num].read.total_bytes = 1;
 | 
			
		||||
      num++;
 | 
			
		||||
    } else {
 | 
			
		||||
      jobs[num].command = I2C_MASTER_CMD_READ;
 | 
			
		||||
      jobs[num].read.ack_value = I2C_ACK_VAL;
 | 
			
		||||
      jobs[num].read.data = (uint8_t *) buf.data;
 | 
			
		||||
      jobs[num].read.total_bytes = buf.len;
 | 
			
		||||
      num++;
 | 
			
		||||
      jobs[num_jobs].command = I2C_MASTER_CMD_READ;
 | 
			
		||||
      jobs[num_jobs].read.ack_value = I2C_NACK_VAL;
 | 
			
		||||
      jobs[num_jobs].read.data = read_buffer + read_count - 1;
 | 
			
		||||
      jobs[num_jobs++].read.total_bytes = 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  jobs[num].command = I2C_MASTER_CMD_STOP;
 | 
			
		||||
  num++;
 | 
			
		||||
 | 
			
		||||
  esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num, 20);
 | 
			
		||||
  jobs[num_jobs++].command = I2C_MASTER_CMD_STOP;
 | 
			
		||||
  ESP_LOGV(TAG, "Sending %zu jobs", num_jobs);
 | 
			
		||||
  esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 20);
 | 
			
		||||
  if (err == ESP_ERR_INVALID_STATE) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X failed: not acked", address);
 | 
			
		||||
    ESP_LOGV(TAG, "TX to %02X failed: not acked", address);
 | 
			
		||||
    return ERROR_NOT_ACKNOWLEDGED;
 | 
			
		||||
  } else if (err == ESP_ERR_TIMEOUT) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X failed: timeout", address);
 | 
			
		||||
    ESP_LOGV(TAG, "TX to %02X failed: timeout", address);
 | 
			
		||||
    return ERROR_TIMEOUT;
 | 
			
		||||
  } else if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    ESP_LOGV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  i2c_cmd_handle_t cmd = i2c_cmd_link_create();
 | 
			
		||||
  esp_err_t err = i2c_master_start(cmd);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X master start failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    i2c_cmd_link_delete(cmd);
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
  err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X address write failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    i2c_cmd_link_delete(cmd);
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    if (buf.len == 0)
 | 
			
		||||
      continue;
 | 
			
		||||
    err = i2c_master_read(cmd, buf.data, buf.len, i == cnt - 1 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGVV(TAG, "RX from %02X data read failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
      i2c_cmd_link_delete(cmd);
 | 
			
		||||
      return ERROR_UNKNOWN;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  err = i2c_master_stop(cmd);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X stop failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    i2c_cmd_link_delete(cmd);
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
  err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS);
 | 
			
		||||
  // i2c_master_cmd_begin() will block for a whole second if no ack:
 | 
			
		||||
  // https://github.com/espressif/esp-idf/issues/4999
 | 
			
		||||
  i2c_cmd_link_delete(cmd);
 | 
			
		||||
  if (err == ESP_FAIL) {
 | 
			
		||||
    // transfer not acked
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X failed: not acked", address);
 | 
			
		||||
    return ERROR_NOT_ACKNOWLEDGED;
 | 
			
		||||
  } else if (err == ESP_ERR_TIMEOUT) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X failed: timeout", address);
 | 
			
		||||
    return ERROR_TIMEOUT;
 | 
			
		||||
  } else if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
  char debug_buf[4];
 | 
			
		||||
  std::string debug_hex;
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    for (size_t j = 0; j < buf.len; j++) {
 | 
			
		||||
      snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
 | 
			
		||||
      debug_hex += debug_buf;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  return ERROR_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
 | 
			
		||||
  // logging is only enabled with vv level, if warnings are shown the caller
 | 
			
		||||
  // should log them
 | 
			
		||||
  if (!initialized_) {
 | 
			
		||||
    ESP_LOGVV(TAG, "i2c bus not initialized!");
 | 
			
		||||
    return ERROR_NOT_INITIALIZED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
 | 
			
		||||
  char debug_buf[4];
 | 
			
		||||
  std::string debug_hex;
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    for (size_t j = 0; j < buf.len; j++) {
 | 
			
		||||
      snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
 | 
			
		||||
      debug_hex += debug_buf;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
 | 
			
		||||
  i2c_operation_job_t jobs[cnt + 3];
 | 
			
		||||
  uint8_t write = (address << 1) | I2C_MASTER_WRITE;
 | 
			
		||||
  size_t num = 0;
 | 
			
		||||
 | 
			
		||||
  jobs[num].command = I2C_MASTER_CMD_START;
 | 
			
		||||
  num++;
 | 
			
		||||
 | 
			
		||||
  jobs[num].command = I2C_MASTER_CMD_WRITE;
 | 
			
		||||
  jobs[num].write.ack_check = true;
 | 
			
		||||
  jobs[num].write.data = &write;
 | 
			
		||||
  jobs[num].write.total_bytes = 1;
 | 
			
		||||
  num++;
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    if (buf.len == 0) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    jobs[num].command = I2C_MASTER_CMD_WRITE;
 | 
			
		||||
    jobs[num].write.ack_check = true;
 | 
			
		||||
    jobs[num].write.data = (uint8_t *) buf.data;
 | 
			
		||||
    jobs[num].write.total_bytes = buf.len;
 | 
			
		||||
    num++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (stop) {
 | 
			
		||||
    jobs[num].command = I2C_MASTER_CMD_STOP;
 | 
			
		||||
    num++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num, 20);
 | 
			
		||||
  if (err == ESP_ERR_INVALID_STATE) {
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X failed: not acked", address);
 | 
			
		||||
    return ERROR_NOT_ACKNOWLEDGED;
 | 
			
		||||
  } else if (err == ESP_ERR_TIMEOUT) {
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X failed: timeout", address);
 | 
			
		||||
    return ERROR_TIMEOUT;
 | 
			
		||||
  } else if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
#else
 | 
			
		||||
  i2c_cmd_handle_t cmd = i2c_cmd_link_create();
 | 
			
		||||
  esp_err_t err = i2c_master_start(cmd);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X master start failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    i2c_cmd_link_delete(cmd);
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
  err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
 | 
			
		||||
  if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X address write failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    i2c_cmd_link_delete(cmd);
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
  for (size_t i = 0; i < cnt; i++) {
 | 
			
		||||
    const auto &buf = buffers[i];
 | 
			
		||||
    if (buf.len == 0)
 | 
			
		||||
      continue;
 | 
			
		||||
    err = i2c_master_write(cmd, buf.data, buf.len, true);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGVV(TAG, "TX to %02X data write failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
      i2c_cmd_link_delete(cmd);
 | 
			
		||||
      return ERROR_UNKNOWN;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (stop) {
 | 
			
		||||
    err = i2c_master_stop(cmd);
 | 
			
		||||
    if (err != ESP_OK) {
 | 
			
		||||
      ESP_LOGVV(TAG, "TX to %02X master stop failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
      i2c_cmd_link_delete(cmd);
 | 
			
		||||
      return ERROR_UNKNOWN;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS);
 | 
			
		||||
  i2c_cmd_link_delete(cmd);
 | 
			
		||||
  if (err == ESP_FAIL) {
 | 
			
		||||
    // transfer not acked
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X failed: not acked", address);
 | 
			
		||||
    return ERROR_NOT_ACKNOWLEDGED;
 | 
			
		||||
  } else if (err == ESP_ERR_TIMEOUT) {
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X failed: timeout", address);
 | 
			
		||||
    return ERROR_TIMEOUT;
 | 
			
		||||
  } else if (err != ESP_OK) {
 | 
			
		||||
    ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
 | 
			
		||||
    return ERROR_UNKNOWN;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  return ERROR_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -436,8 +190,8 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, b
 | 
			
		||||
void IDFI2CBus::recover_() {
 | 
			
		||||
  ESP_LOGI(TAG, "Performing bus recovery");
 | 
			
		||||
 | 
			
		||||
  const gpio_num_t scl_pin = static_cast<gpio_num_t>(scl_pin_);
 | 
			
		||||
  const gpio_num_t sda_pin = static_cast<gpio_num_t>(sda_pin_);
 | 
			
		||||
  const auto scl_pin = static_cast<gpio_num_t>(scl_pin_);
 | 
			
		||||
  const auto sda_pin = static_cast<gpio_num_t>(sda_pin_);
 | 
			
		||||
 | 
			
		||||
  // For the upcoming operations, target for a 60kHz toggle frequency.
 | 
			
		||||
  // 1000kHz is the maximum frequency for I2C running in standard-mode,
 | 
			
		||||
@@ -545,5 +299,4 @@ void IDFI2CBus::recover_() {
 | 
			
		||||
 | 
			
		||||
}  // namespace i2c
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP_IDF
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,9 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
 | 
			
		||||
#include "esp_idf_version.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "i2c_bus.h"
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
 | 
			
		||||
#include <driver/i2c_master.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <driver/i2c.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace i2c {
 | 
			
		||||
@@ -24,36 +19,33 @@ class IDFI2CBus : public InternalI2CBus, public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
 | 
			
		||||
  ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
 | 
			
		||||
  ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
 | 
			
		||||
                        size_t read_count) override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::BUS; }
 | 
			
		||||
 | 
			
		||||
  void set_scan(bool scan) { scan_ = scan; }
 | 
			
		||||
  void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; }
 | 
			
		||||
  void set_sda_pullup_enabled(bool sda_pullup_enabled) { sda_pullup_enabled_ = sda_pullup_enabled; }
 | 
			
		||||
  void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; }
 | 
			
		||||
  void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; }
 | 
			
		||||
  void set_frequency(uint32_t frequency) { frequency_ = frequency; }
 | 
			
		||||
  void set_timeout(uint32_t timeout) { timeout_ = timeout; }
 | 
			
		||||
  void set_scan(bool scan) { this->scan_ = scan; }
 | 
			
		||||
  void set_sda_pin(uint8_t sda_pin) { this->sda_pin_ = sda_pin; }
 | 
			
		||||
  void set_sda_pullup_enabled(bool sda_pullup_enabled) { this->sda_pullup_enabled_ = sda_pullup_enabled; }
 | 
			
		||||
  void set_scl_pin(uint8_t scl_pin) { this->scl_pin_ = scl_pin; }
 | 
			
		||||
  void set_scl_pullup_enabled(bool scl_pullup_enabled) { this->scl_pullup_enabled_ = scl_pullup_enabled; }
 | 
			
		||||
  void set_frequency(uint32_t frequency) { this->frequency_ = frequency; }
 | 
			
		||||
  void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
 | 
			
		||||
 | 
			
		||||
  int get_port() const override { return static_cast<int>(this->port_); }
 | 
			
		||||
  int get_port() const override { return this->port_; }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void recover_();
 | 
			
		||||
  RecoveryCode recovery_result_;
 | 
			
		||||
  RecoveryCode recovery_result_{};
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
 | 
			
		||||
  i2c_master_dev_handle_t dev_;
 | 
			
		||||
  i2c_master_bus_handle_t bus_;
 | 
			
		||||
  void i2c_scan() override;
 | 
			
		||||
#endif
 | 
			
		||||
  i2c_port_t port_;
 | 
			
		||||
  uint8_t sda_pin_;
 | 
			
		||||
  bool sda_pullup_enabled_;
 | 
			
		||||
  uint8_t scl_pin_;
 | 
			
		||||
  bool scl_pullup_enabled_;
 | 
			
		||||
  uint32_t frequency_;
 | 
			
		||||
  i2c_master_dev_handle_t dev_{};
 | 
			
		||||
  i2c_master_bus_handle_t bus_{};
 | 
			
		||||
  i2c_port_t port_{};
 | 
			
		||||
  uint8_t sda_pin_{};
 | 
			
		||||
  bool sda_pullup_enabled_{};
 | 
			
		||||
  uint8_t scl_pin_{};
 | 
			
		||||
  bool scl_pullup_enabled_{};
 | 
			
		||||
  uint32_t frequency_{};
 | 
			
		||||
  uint32_t timeout_ = 0;
 | 
			
		||||
  bool initialized_ = false;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -212,7 +212,7 @@ def validate_use_legacy(value):
 | 
			
		||||
                f"All i2s_audio components must set {CONF_USE_LEGACY} to the same value."
 | 
			
		||||
            )
 | 
			
		||||
        if (not value[CONF_USE_LEGACY]) and (CORE.using_arduino):
 | 
			
		||||
            raise cv.Invalid("Arduino supports only the legacy i2s driver.")
 | 
			
		||||
            raise cv.Invalid("Arduino supports only the legacy i2s driver")
 | 
			
		||||
        _use_legacy_driver = value[CONF_USE_LEGACY]
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
def _final_validate(_):
 | 
			
		||||
    if not use_legacy():
 | 
			
		||||
        raise cv.Invalid("I2S media player is only compatible with legacy i2s driver.")
 | 
			
		||||
        raise cv.Invalid("I2S media player is only compatible with legacy i2s driver")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = _final_validate
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 | 
			
		||||
def _final_validate(config):
 | 
			
		||||
    if not use_legacy() and config[CONF_ADC_TYPE] == "internal":
 | 
			
		||||
        raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver.")
 | 
			
		||||
        raise cv.Invalid("Internal ADC is only compatible with legacy i2s driver")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = _final_validate
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
def _final_validate(config):
 | 
			
		||||
    if not use_legacy():
 | 
			
		||||
        if config[CONF_DAC_TYPE] == "internal":
 | 
			
		||||
            raise cv.Invalid("Internal DAC is only compatible with legacy i2s driver.")
 | 
			
		||||
            raise cv.Invalid("Internal DAC is only compatible with legacy i2s driver")
 | 
			
		||||
        if config[CONF_I2S_COMM_FMT] == "stand_max":
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                "I2S standard max format only implemented with legacy i2s driver."
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ void IAQCore::setup() {
 | 
			
		||||
void IAQCore::update() {
 | 
			
		||||
  uint8_t buffer[sizeof(SensorData)];
 | 
			
		||||
 | 
			
		||||
  if (this->read_register(0xB5, buffer, sizeof(buffer), false) != i2c::ERROR_OK) {
 | 
			
		||||
  if (this->read_register(0xB5, buffer, sizeof(buffer)) != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGD(TAG, "Read failed");
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
    this->publish_nans_();
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ void INA2XXI2C::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool INA2XXI2C::read_ina_register(uint8_t reg, uint8_t *data, size_t len) {
 | 
			
		||||
  auto ret = this->read_register(reg, data, len, false);
 | 
			
		||||
  auto ret = this->read_register(reg, data, len);
 | 
			
		||||
  if (ret != i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGE(TAG, "read_ina_register_ failed. Reg=0x%02X Err=%d", reg, ret);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ static const char *const TAG = "integration";
 | 
			
		||||
 | 
			
		||||
void IntegrationSensor::setup() {
 | 
			
		||||
  if (this->restore_) {
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
 | 
			
		||||
    float preference_value = 0;
 | 
			
		||||
    this->pref_.load(&preference_value);
 | 
			
		||||
    this->result_ = preference_value;
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ void KMeterISOComponent::setup() {
 | 
			
		||||
    this->reset_to_construction_state();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto err = this->bus_->writev(this->address_, nullptr, 0);
 | 
			
		||||
  auto err = this->bus_->write_readv(this->address_, nullptr, 0, nullptr, 0);
 | 
			
		||||
  if (err == esphome::i2c::ERROR_OK) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "Could write to the address %d.", this->address_);
 | 
			
		||||
  } else {
 | 
			
		||||
@@ -33,7 +33,7 @@ void KMeterISOComponent::setup() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t read_buf[4] = {1};
 | 
			
		||||
  if (!this->read_bytes(KMETER_ERROR_STATUS_REG, read_buf, 1)) {
 | 
			
		||||
  if (!this->read_register(KMETER_ERROR_STATUS_REG, read_buf, 1)) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "Could not read from the device.");
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "lc709203f.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace lc709203f {
 | 
			
		||||
@@ -184,12 +185,12 @@ uint8_t Lc709203f::get_register_(uint8_t register_to_read, uint16_t *register_va
 | 
			
		||||
    //  function will send a stop between the read and the write portion of the I2C
 | 
			
		||||
    //  transaction. This is bad in this case and will result in reading nothing but 0xFFFF
 | 
			
		||||
    //  from the registers.
 | 
			
		||||
    return_code = this->read_register(register_to_read, &read_buffer[3], 3, false);
 | 
			
		||||
    return_code = this->read_register(register_to_read, &read_buffer[3], 3);
 | 
			
		||||
    if (return_code != i2c::NO_ERROR) {
 | 
			
		||||
      // Error on the i2c bus
 | 
			
		||||
      this->status_set_warning(
 | 
			
		||||
          str_sprintf("Error code %d when reading from register 0x%02X", return_code, register_to_read).c_str());
 | 
			
		||||
    } else if (this->crc8_(read_buffer, 5) != read_buffer[5]) {
 | 
			
		||||
    } else if (crc8(read_buffer, 5, 0x00, 0x07, true) != read_buffer[5]) {
 | 
			
		||||
      // I2C indicated OK, but the CRC of the data does not matcth.
 | 
			
		||||
      this->status_set_warning(str_sprintf("CRC error reading from register 0x%02X", register_to_read).c_str());
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -220,12 +221,12 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set)
 | 
			
		||||
  write_buffer[1] = register_to_set;
 | 
			
		||||
  write_buffer[2] = value_to_set & 0xFF;         // Low byte
 | 
			
		||||
  write_buffer[3] = (value_to_set >> 8) & 0xFF;  // High byte
 | 
			
		||||
  write_buffer[4] = this->crc8_(write_buffer, 4);
 | 
			
		||||
  write_buffer[4] = crc8(write_buffer, 4, 0x00, 0x07, true);
 | 
			
		||||
 | 
			
		||||
  for (uint8_t i = 0; i <= LC709203F_I2C_RETRY_COUNT; i++) {
 | 
			
		||||
    // Note: we don't write the first byte of the write buffer to the device.
 | 
			
		||||
    //  This is done automatically by the write() function.
 | 
			
		||||
    return_code = this->write(&write_buffer[1], 4, true);
 | 
			
		||||
    return_code = this->write(&write_buffer[1], 4);
 | 
			
		||||
    if (return_code == i2c::NO_ERROR) {
 | 
			
		||||
      return return_code;
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -239,20 +240,6 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set)
 | 
			
		||||
  return return_code;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t Lc709203f::crc8_(uint8_t *byte_buffer, uint8_t length_of_crc) {
 | 
			
		||||
  uint8_t crc = 0x00;
 | 
			
		||||
  const uint8_t polynomial(0x07);
 | 
			
		||||
 | 
			
		||||
  for (uint8_t j = length_of_crc; j; --j) {
 | 
			
		||||
    crc ^= *byte_buffer++;
 | 
			
		||||
 | 
			
		||||
    for (uint8_t i = 8; i; --i) {
 | 
			
		||||
      crc = (crc & 0x80) ? (crc << 1) ^ polynomial : (crc << 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return crc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Lc709203f::set_pack_size(uint16_t pack_size) {
 | 
			
		||||
  static const uint16_t PACK_SIZE_ARRAY[6] = {100, 200, 500, 1000, 2000, 3000};
 | 
			
		||||
  static const uint16_t APA_ARRAY[6] = {0x08, 0x0B, 0x10, 0x19, 0x2D, 0x36};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace lc709203f {
 | 
			
		||||
@@ -38,7 +38,6 @@ class Lc709203f : public sensor::Sensor, public PollingComponent, public i2c::I2
 | 
			
		||||
 private:
 | 
			
		||||
  uint8_t get_register_(uint8_t register_to_read, uint16_t *register_value);
 | 
			
		||||
  uint8_t set_register_(uint8_t register_to_set, uint16_t value_to_set);
 | 
			
		||||
  uint8_t crc8_(uint8_t *byte_buffer, uint8_t length_of_crc);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
#include "text_sensor.h"
 | 
			
		||||
#include "ld2420_text_sensor.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
@@ -184,7 +184,7 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui
 | 
			
		||||
void LD2450Component::setup() {
 | 
			
		||||
#ifdef USE_NUMBER
 | 
			
		||||
  if (this->presence_timeout_number_ != nullptr) {
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_object_id_hash());
 | 
			
		||||
    this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_preference_hash());
 | 
			
		||||
    this->set_presence_timeout();
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
#include <flashdb.h>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -139,21 +139,29 @@ class LibreTinyPreferences : public ESPPreferences {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) {
 | 
			
		||||
    NVSData stored_data{};
 | 
			
		||||
    struct fdb_kv kv;
 | 
			
		||||
    fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv);
 | 
			
		||||
    if (kvp == nullptr) {
 | 
			
		||||
      ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str());
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    stored_data.data.resize(kv.value_len);
 | 
			
		||||
    fdb_blob_make(&blob, stored_data.data.data(), kv.value_len);
 | 
			
		||||
 | 
			
		||||
    // Check size first - if different, data has changed
 | 
			
		||||
    if (kv.value_len != to_save.data.size()) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Allocate buffer on heap to avoid stack allocation for large data
 | 
			
		||||
    auto stored_data = std::make_unique<uint8_t[]>(kv.value_len);
 | 
			
		||||
    fdb_blob_make(&blob, stored_data.get(), kv.value_len);
 | 
			
		||||
    size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob);
 | 
			
		||||
    if (actual_len != kv.value_len) {
 | 
			
		||||
      ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return to_save.data != stored_data.data;
 | 
			
		||||
 | 
			
		||||
    // Compare the actual data
 | 
			
		||||
    return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool reset() override {
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,13 @@ class AddressableLightEffect : public LightEffect {
 | 
			
		||||
    this->apply(*this->get_addressable_(), current_color);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get effect index specifically for addressable effects.
 | 
			
		||||
  /// Can be used by effects to modify behavior based on their position in the list.
 | 
			
		||||
  uint32_t get_effect_index() const { return this->get_index(); }
 | 
			
		||||
 | 
			
		||||
  /// Check if this is the currently running addressable effect.
 | 
			
		||||
  bool is_current_effect() const { return this->is_active() && this->get_addressable_()->is_effect_active(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  AddressableLight *get_addressable_() const { return (AddressableLight *) this->state_->get_output(); }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -125,6 +125,10 @@ class LambdaLightEffect : public LightEffect {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the current effect index for use in lambda functions.
 | 
			
		||||
  /// This can be useful for lambda effects that need to know their own index.
 | 
			
		||||
  uint32_t get_current_index() const { return this->get_index(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::function<void(bool initial_run)> f_;
 | 
			
		||||
  uint32_t update_interval_;
 | 
			
		||||
@@ -143,6 +147,10 @@ class AutomationLightEffect : public LightEffect {
 | 
			
		||||
  }
 | 
			
		||||
  Trigger<> *get_trig() const { return trig_; }
 | 
			
		||||
 | 
			
		||||
  /// Get the current effect index for use in automations.
 | 
			
		||||
  /// Useful for automations that need to know which effect is running.
 | 
			
		||||
  uint32_t get_current_index() const { return this->get_index(); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  Trigger<> *trig_{new Trigger<>};
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								esphome/components/light/light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								esphome/components/light/light_effect.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
#include "light_effect.h"
 | 
			
		||||
#include "light_state.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace light {
 | 
			
		||||
 | 
			
		||||
uint32_t LightEffect::get_index() const {
 | 
			
		||||
  if (this->state_ == nullptr) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  return this->get_index_in_parent_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LightEffect::is_active() const {
 | 
			
		||||
  if (this->state_ == nullptr) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return this->get_index() != 0 && this->state_->get_current_effect_index() == this->get_index();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t LightEffect::get_index_in_parent_() const {
 | 
			
		||||
  if (this->state_ == nullptr) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const auto &effects = this->state_->get_effects();
 | 
			
		||||
  for (size_t i = 0; i < effects.size(); i++) {
 | 
			
		||||
    if (effects[i] == this) {
 | 
			
		||||
      return i + 1;  // Effects are 1-indexed in the API
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return 0;  // Not found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace light
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -34,9 +34,23 @@ class LightEffect {
 | 
			
		||||
    this->init();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get the index of this effect in the parent light's effect list.
 | 
			
		||||
  /// Returns 0 if not found or not initialized.
 | 
			
		||||
  uint32_t get_index() const;
 | 
			
		||||
 | 
			
		||||
  /// Check if this effect is currently active.
 | 
			
		||||
  bool is_active() const;
 | 
			
		||||
 | 
			
		||||
  /// Get a reference to the parent light state.
 | 
			
		||||
  /// Returns nullptr if not initialized.
 | 
			
		||||
  LightState *get_light_state() const { return this->state_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  LightState *state_{nullptr};
 | 
			
		||||
  std::string name_;
 | 
			
		||||
 | 
			
		||||
  /// Internal method to find this effect's index in the parent light's effect list.
 | 
			
		||||
  uint32_t get_index_in_parent_() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace light
 | 
			
		||||
 
 | 
			
		||||
@@ -36,8 +36,11 @@ static constexpr const char *get_color_mode_json_str(ColorMode mode) {
 | 
			
		||||
 | 
			
		||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
 | 
			
		||||
  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
 | 
			
		||||
  if (state.supports_effects())
 | 
			
		||||
  if (state.supports_effects()) {
 | 
			
		||||
    root["effect"] = state.get_effect_name();
 | 
			
		||||
    root["effect_index"] = state.get_current_effect_index();
 | 
			
		||||
    root["effect_count"] = state.get_effect_count();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto values = state.remote_values;
 | 
			
		||||
  auto traits = state.get_output()->get_traits();
 | 
			
		||||
@@ -160,6 +163,11 @@ void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject
 | 
			
		||||
    const char *effect = root["effect"];
 | 
			
		||||
    call.set_effect(effect);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (root["effect_index"].is<uint32_t>()) {
 | 
			
		||||
    uint32_t effect_index = root["effect_index"];
 | 
			
		||||
    call.set_effect(effect_index);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace light
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ void LightState::setup() {
 | 
			
		||||
    case LIGHT_RESTORE_DEFAULT_ON:
 | 
			
		||||
    case LIGHT_RESTORE_INVERTED_DEFAULT_OFF:
 | 
			
		||||
    case LIGHT_RESTORE_INVERTED_DEFAULT_ON:
 | 
			
		||||
      this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_object_id_hash());
 | 
			
		||||
      this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash());
 | 
			
		||||
      // Attempt to load from preferences, else fall back to default values
 | 
			
		||||
      if (!this->rtc_.load(&recovered)) {
 | 
			
		||||
        recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON ||
 | 
			
		||||
@@ -54,7 +54,7 @@ void LightState::setup() {
 | 
			
		||||
      break;
 | 
			
		||||
    case LIGHT_RESTORE_AND_OFF:
 | 
			
		||||
    case LIGHT_RESTORE_AND_ON:
 | 
			
		||||
      this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_object_id_hash());
 | 
			
		||||
      this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash());
 | 
			
		||||
      this->rtc_.load(&recovered);
 | 
			
		||||
      recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON);
 | 
			
		||||
      break;
 | 
			
		||||
@@ -140,12 +140,22 @@ float LightState::get_setup_priority() const { return setup_priority::HARDWARE -
 | 
			
		||||
void LightState::publish_state() { this->remote_values_callback_.call(); }
 | 
			
		||||
 | 
			
		||||
LightOutput *LightState::get_output() const { return this->output_; }
 | 
			
		||||
 | 
			
		||||
static constexpr const char *EFFECT_NONE = "None";
 | 
			
		||||
static constexpr auto EFFECT_NONE_REF = StringRef::from_lit("None");
 | 
			
		||||
 | 
			
		||||
std::string LightState::get_effect_name() {
 | 
			
		||||
  if (this->active_effect_index_ > 0) {
 | 
			
		||||
    return this->effects_[this->active_effect_index_ - 1]->get_name();
 | 
			
		||||
  } else {
 | 
			
		||||
    return "None";
 | 
			
		||||
  }
 | 
			
		||||
  return EFFECT_NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
StringRef LightState::get_effect_name_ref() {
 | 
			
		||||
  if (this->active_effect_index_ > 0) {
 | 
			
		||||
    return StringRef(this->effects_[this->active_effect_index_ - 1]->get_name());
 | 
			
		||||
  }
 | 
			
		||||
  return EFFECT_NONE_REF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
#include "esphome/core/entity_base.h"
 | 
			
		||||
#include "esphome/core/optional.h"
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
#include "esphome/core/string_ref.h"
 | 
			
		||||
#include "light_call.h"
 | 
			
		||||
#include "light_color_values.h"
 | 
			
		||||
#include "light_effect.h"
 | 
			
		||||
@@ -116,6 +117,8 @@ class LightState : public EntityBase, public Component {
 | 
			
		||||
 | 
			
		||||
  /// Return the name of the current effect, or if no effect is active "None".
 | 
			
		||||
  std::string get_effect_name();
 | 
			
		||||
  /// Return the name of the current effect as StringRef (for API usage)
 | 
			
		||||
  StringRef get_effect_name_ref();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This lets front-end components subscribe to light change events. This callback is called once
 | 
			
		||||
@@ -160,6 +163,44 @@ class LightState : public EntityBase, public Component {
 | 
			
		||||
  /// Add effects for this light state.
 | 
			
		||||
  void add_effects(const std::vector<LightEffect *> &effects);
 | 
			
		||||
 | 
			
		||||
  /// Get the total number of effects available for this light.
 | 
			
		||||
  size_t get_effect_count() const { return this->effects_.size(); }
 | 
			
		||||
 | 
			
		||||
  /// Get the currently active effect index (0 = no effect, 1+ = effect index).
 | 
			
		||||
  uint32_t get_current_effect_index() const { return this->active_effect_index_; }
 | 
			
		||||
 | 
			
		||||
  /// Get effect index by name. Returns 0 if effect not found.
 | 
			
		||||
  uint32_t get_effect_index(const std::string &effect_name) const {
 | 
			
		||||
    if (strcasecmp(effect_name.c_str(), "none") == 0) {
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
    for (size_t i = 0; i < this->effects_.size(); i++) {
 | 
			
		||||
      if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name().c_str()) == 0) {
 | 
			
		||||
        return i + 1;  // Effects are 1-indexed in active_effect_index_
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return 0;  // Effect not found
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get effect by index. Returns nullptr if index is invalid.
 | 
			
		||||
  LightEffect *get_effect_by_index(uint32_t index) const {
 | 
			
		||||
    if (index == 0 || index > this->effects_.size()) {
 | 
			
		||||
      return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
    return this->effects_[index - 1];  // Effects are 1-indexed in active_effect_index_
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get effect name by index. Returns "None" for index 0, empty string for invalid index.
 | 
			
		||||
  std::string get_effect_name_by_index(uint32_t index) const {
 | 
			
		||||
    if (index == 0) {
 | 
			
		||||
      return "None";
 | 
			
		||||
    }
 | 
			
		||||
    if (index > this->effects_.size()) {
 | 
			
		||||
      return "";  // Invalid index
 | 
			
		||||
    }
 | 
			
		||||
    return this->effects_[index - 1]->get_name();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// The result of all the current_values_as_* methods have gamma correction applied.
 | 
			
		||||
  void current_values_as_binary(bool *binary);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ class LVGLNumber : public number::Number, public Component {
 | 
			
		||||
  void setup() override {
 | 
			
		||||
    float value = this->value_lambda_();
 | 
			
		||||
    if (this->restore_) {
 | 
			
		||||
      this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
 | 
			
		||||
      this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
 | 
			
		||||
      if (this->pref_.load(&value)) {
 | 
			
		||||
        this->control_lambda_(value);
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ class LVGLSelect : public select::Select, public Component {
 | 
			
		||||
    this->set_options_();
 | 
			
		||||
    if (this->restore_) {
 | 
			
		||||
      size_t index;
 | 
			
		||||
      this->pref_ = global_preferences->make_preference<size_t>(this->get_object_id_hash());
 | 
			
		||||
      this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
 | 
			
		||||
      if (this->pref_.load(&index))
 | 
			
		||||
        this->widget_->set_selected_index(index, LV_ANIM_OFF);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ from ..defines import (
 | 
			
		||||
    literal,
 | 
			
		||||
)
 | 
			
		||||
from ..lv_validation import (
 | 
			
		||||
    lv_angle,
 | 
			
		||||
    lv_angle_degrees,
 | 
			
		||||
    lv_bool,
 | 
			
		||||
    lv_color,
 | 
			
		||||
    lv_image,
 | 
			
		||||
@@ -395,15 +395,15 @@ ARC_PROPS = {
 | 
			
		||||
    DRAW_OPA_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_RADIUS): pixels,
 | 
			
		||||
            cv.Required(CONF_START_ANGLE): lv_angle,
 | 
			
		||||
            cv.Required(CONF_END_ANGLE): lv_angle,
 | 
			
		||||
            cv.Required(CONF_START_ANGLE): lv_angle_degrees,
 | 
			
		||||
            cv.Required(CONF_END_ANGLE): lv_angle_degrees,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend({cv.Optional(prop): validator for prop, validator in ARC_PROPS.items()}),
 | 
			
		||||
)
 | 
			
		||||
async def canvas_draw_arc(config, action_id, template_arg, args):
 | 
			
		||||
    radius = await size.process(config[CONF_RADIUS])
 | 
			
		||||
    start_angle = await lv_angle.process(config[CONF_START_ANGLE])
 | 
			
		||||
    end_angle = await lv_angle.process(config[CONF_END_ANGLE])
 | 
			
		||||
    start_angle = await lv_angle_degrees.process(config[CONF_START_ANGLE])
 | 
			
		||||
    end_angle = await lv_angle_degrees.process(config[CONF_END_ANGLE])
 | 
			
		||||
 | 
			
		||||
    async def do_draw_arc(w: Widget, x, y, dsc_addr):
 | 
			
		||||
        lv.canvas_draw_arc(w.obj, x, y, radius, start_angle, end_angle, dsc_addr)
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ from esphome.const import (
 | 
			
		||||
    CONF_VALUE,
 | 
			
		||||
    CONF_WIDTH,
 | 
			
		||||
)
 | 
			
		||||
from esphome.cpp_generator import IntLiteral
 | 
			
		||||
 | 
			
		||||
from ..automation import action_to_code
 | 
			
		||||
from ..defines import (
 | 
			
		||||
@@ -32,7 +31,7 @@ from ..helpers import add_lv_use, lvgl_components_required
 | 
			
		||||
from ..lv_validation import (
 | 
			
		||||
    get_end_value,
 | 
			
		||||
    get_start_value,
 | 
			
		||||
    lv_angle,
 | 
			
		||||
    lv_angle_degrees,
 | 
			
		||||
    lv_bool,
 | 
			
		||||
    lv_color,
 | 
			
		||||
    lv_float,
 | 
			
		||||
@@ -163,7 +162,7 @@ SCALE_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_,
 | 
			
		||||
        cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_,
 | 
			
		||||
        cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360),
 | 
			
		||||
        cv.Optional(CONF_ROTATION): lv_angle,
 | 
			
		||||
        cv.Optional(CONF_ROTATION): lv_angle_degrees,
 | 
			
		||||
        cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
@@ -188,9 +187,7 @@ class MeterType(WidgetType):
 | 
			
		||||
        for scale_conf in config.get(CONF_SCALES, ()):
 | 
			
		||||
            rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2
 | 
			
		||||
            if CONF_ROTATION in scale_conf:
 | 
			
		||||
                rotation = await lv_angle.process(scale_conf[CONF_ROTATION])
 | 
			
		||||
                if isinstance(rotation, IntLiteral):
 | 
			
		||||
                    rotation = int(str(rotation)) // 10
 | 
			
		||||
                rotation = await lv_angle_degrees.process(scale_conf[CONF_ROTATION])
 | 
			
		||||
            with LocalVariable(
 | 
			
		||||
                "meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var)
 | 
			
		||||
            ) as meter_var:
 | 
			
		||||
 
 | 
			
		||||
@@ -328,7 +328,7 @@ bool Mcp4461Component::increase_wiper_(Mcp4461WiperIdx wiper) {
 | 
			
		||||
  ESP_LOGV(TAG, "Increasing wiper %u", wiper_idx);
 | 
			
		||||
  uint8_t addr = this->get_wiper_address_(wiper_idx);
 | 
			
		||||
  uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::INCREMENT);
 | 
			
		||||
  auto err = this->write(&this->address_, reg, sizeof(reg));
 | 
			
		||||
  auto err = this->write(&this->address_, reg);
 | 
			
		||||
  if (err != i2c::ERROR_OK) {
 | 
			
		||||
    this->error_code_ = MCP4461_STATUS_I2C_ERROR;
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
@@ -359,7 +359,7 @@ bool Mcp4461Component::decrease_wiper_(Mcp4461WiperIdx wiper) {
 | 
			
		||||
  ESP_LOGV(TAG, "Decreasing wiper %u", wiper_idx);
 | 
			
		||||
  uint8_t addr = this->get_wiper_address_(wiper_idx);
 | 
			
		||||
  uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::DECREMENT);
 | 
			
		||||
  auto err = this->write(&this->address_, reg, sizeof(reg));
 | 
			
		||||
  auto err = this->write(&this->address_, reg);
 | 
			
		||||
  if (err != i2c::ERROR_OK) {
 | 
			
		||||
    this->error_code_ = MCP4461_STATUS_I2C_ERROR;
 | 
			
		||||
    this->status_set_warning();
 | 
			
		||||
 
 | 
			
		||||
@@ -24,100 +24,139 @@ static const char *const TAG = "mdns";
 | 
			
		||||
void MDNSComponent::compile_records_() {
 | 
			
		||||
  this->hostname_ = App.get_name();
 | 
			
		||||
 | 
			
		||||
  this->services_.clear();
 | 
			
		||||
  // Calculate exact capacity needed for services vector
 | 
			
		||||
  size_t services_count = 0;
 | 
			
		||||
#ifdef USE_API
 | 
			
		||||
  if (api::global_api_server != nullptr) {
 | 
			
		||||
    MDNSService service{};
 | 
			
		||||
    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
 | 
			
		||||
  if (api::global_api_server != nullptr) {
 | 
			
		||||
    this->services_.emplace_back();
 | 
			
		||||
    auto &service = this->services_.back();
 | 
			
		||||
    service.service_type = "_esphomelib";
 | 
			
		||||
    service.proto = "_tcp";
 | 
			
		||||
    service.port = api::global_api_server->get_port();
 | 
			
		||||
    if (!App.get_friendly_name().empty()) {
 | 
			
		||||
      service.txt_records.push_back({"friendly_name", App.get_friendly_name()});
 | 
			
		||||
    }
 | 
			
		||||
    service.txt_records.push_back({"version", ESPHOME_VERSION});
 | 
			
		||||
    service.txt_records.push_back({"mac", get_mac_address()});
 | 
			
		||||
    const char *platform = nullptr;
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
    platform = "ESP8266";
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
    platform = "ESP32";
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_RP2040
 | 
			
		||||
    platform = "RP2040";
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_LIBRETINY
 | 
			
		||||
    platform = lt_cpu_get_model_name();
 | 
			
		||||
#endif
 | 
			
		||||
    if (platform != nullptr) {
 | 
			
		||||
      service.txt_records.push_back({"platform", platform});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    service.txt_records.push_back({"board", ESPHOME_BOARD});
 | 
			
		||||
    const std::string &friendly_name = App.get_friendly_name();
 | 
			
		||||
    bool friendly_name_empty = friendly_name.empty();
 | 
			
		||||
 | 
			
		||||
    // Calculate exact capacity for txt_records
 | 
			
		||||
    size_t txt_count = 3;  // version, mac, board (always present)
 | 
			
		||||
    if (!friendly_name_empty) {
 | 
			
		||||
      txt_count++;  // friendly_name
 | 
			
		||||
    }
 | 
			
		||||
#if defined(USE_ESP8266) || defined(USE_ESP32) || defined(USE_RP2040) || defined(USE_LIBRETINY)
 | 
			
		||||
    txt_count++;  // platform
 | 
			
		||||
#endif
 | 
			
		||||
#if defined(USE_WIFI) || defined(USE_ETHERNET) || defined(USE_OPENTHREAD)
 | 
			
		||||
    txt_count++;  // network
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
    txt_count++;  // api_encryption or api_encryption_supported
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef ESPHOME_PROJECT_NAME
 | 
			
		||||
    txt_count += 2;  // project_name and project_version
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_DASHBOARD_IMPORT
 | 
			
		||||
    txt_count++;  // package_import_url
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    auto &txt_records = service.txt_records;
 | 
			
		||||
    txt_records.reserve(txt_count);
 | 
			
		||||
 | 
			
		||||
    if (!friendly_name_empty) {
 | 
			
		||||
      txt_records.emplace_back(MDNSTXTRecord{"friendly_name", friendly_name});
 | 
			
		||||
    }
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"version", ESPHOME_VERSION});
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"mac", get_mac_address()});
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP8266"});
 | 
			
		||||
#elif defined(USE_ESP32)
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"platform", "ESP32"});
 | 
			
		||||
#elif defined(USE_RP2040)
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"platform", "RP2040"});
 | 
			
		||||
#elif defined(USE_LIBRETINY)
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"platform", lt_cpu_get_model_name()});
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"board", ESPHOME_BOARD});
 | 
			
		||||
 | 
			
		||||
#if defined(USE_WIFI)
 | 
			
		||||
    service.txt_records.push_back({"network", "wifi"});
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"network", "wifi"});
 | 
			
		||||
#elif defined(USE_ETHERNET)
 | 
			
		||||
    service.txt_records.push_back({"network", "ethernet"});
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"network", "ethernet"});
 | 
			
		||||
#elif defined(USE_OPENTHREAD)
 | 
			
		||||
    service.txt_records.push_back({"network", "thread"});
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"network", "thread"});
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_API_NOISE
 | 
			
		||||
    static constexpr const char *NOISE_ENCRYPTION = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
 | 
			
		||||
    if (api::global_api_server->get_noise_ctx()->has_psk()) {
 | 
			
		||||
      service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
 | 
			
		||||
      txt_records.emplace_back(MDNSTXTRecord{"api_encryption", NOISE_ENCRYPTION});
 | 
			
		||||
    } else {
 | 
			
		||||
      service.txt_records.push_back({"api_encryption_supported", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"});
 | 
			
		||||
      txt_records.emplace_back(MDNSTXTRecord{"api_encryption_supported", NOISE_ENCRYPTION});
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef ESPHOME_PROJECT_NAME
 | 
			
		||||
    service.txt_records.push_back({"project_name", ESPHOME_PROJECT_NAME});
 | 
			
		||||
    service.txt_records.push_back({"project_version", ESPHOME_PROJECT_VERSION});
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"project_name", ESPHOME_PROJECT_NAME});
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"project_version", ESPHOME_PROJECT_VERSION});
 | 
			
		||||
#endif  // ESPHOME_PROJECT_NAME
 | 
			
		||||
 | 
			
		||||
#ifdef USE_DASHBOARD_IMPORT
 | 
			
		||||
    service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()});
 | 
			
		||||
    txt_records.emplace_back(MDNSTXTRecord{"package_import_url", dashboard_import::get_package_import_url()});
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    this->services_.push_back(service);
 | 
			
		||||
  }
 | 
			
		||||
#endif  // USE_API
 | 
			
		||||
 | 
			
		||||
#ifdef USE_PROMETHEUS
 | 
			
		||||
  {
 | 
			
		||||
    MDNSService service{};
 | 
			
		||||
    service.service_type = "_prometheus-http";
 | 
			
		||||
    service.proto = "_tcp";
 | 
			
		||||
    service.port = USE_WEBSERVER_PORT;
 | 
			
		||||
    this->services_.push_back(service);
 | 
			
		||||
  }
 | 
			
		||||
  this->services_.emplace_back();
 | 
			
		||||
  auto &prom_service = this->services_.back();
 | 
			
		||||
  prom_service.service_type = "_prometheus-http";
 | 
			
		||||
  prom_service.proto = "_tcp";
 | 
			
		||||
  prom_service.port = USE_WEBSERVER_PORT;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_WEBSERVER
 | 
			
		||||
  {
 | 
			
		||||
    MDNSService service{};
 | 
			
		||||
    service.service_type = "_http";
 | 
			
		||||
    service.proto = "_tcp";
 | 
			
		||||
    service.port = USE_WEBSERVER_PORT;
 | 
			
		||||
    this->services_.push_back(service);
 | 
			
		||||
  }
 | 
			
		||||
  this->services_.emplace_back();
 | 
			
		||||
  auto &web_service = this->services_.back();
 | 
			
		||||
  web_service.service_type = "_http";
 | 
			
		||||
  web_service.proto = "_tcp";
 | 
			
		||||
  web_service.port = USE_WEBSERVER_PORT;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_MDNS_EXTRA_SERVICES
 | 
			
		||||
  this->services_.insert(this->services_.end(), this->services_extra_.begin(), this->services_extra_.end());
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (this->services_.empty()) {
 | 
			
		||||
    // Publish "http" service if not using native API
 | 
			
		||||
    // This is just to have *some* mDNS service so that .local resolution works
 | 
			
		||||
    MDNSService service{};
 | 
			
		||||
    service.service_type = "_http";
 | 
			
		||||
    service.proto = "_tcp";
 | 
			
		||||
    service.port = USE_WEBSERVER_PORT;
 | 
			
		||||
    service.txt_records.push_back({"version", ESPHOME_VERSION});
 | 
			
		||||
    this->services_.push_back(service);
 | 
			
		||||
  }
 | 
			
		||||
#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
 | 
			
		||||
  // This is just to have *some* mDNS service so that .local resolution works
 | 
			
		||||
  this->services_.emplace_back();
 | 
			
		||||
  auto &fallback_service = this->services_.back();
 | 
			
		||||
  fallback_service.service_type = "_http";
 | 
			
		||||
  fallback_service.proto = "_tcp";
 | 
			
		||||
  fallback_service.port = USE_WEBSERVER_PORT;
 | 
			
		||||
  fallback_service.txt_records.emplace_back(MDNSTXTRecord{"version", ESPHOME_VERSION});
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MDNSComponent::dump_config() {
 | 
			
		||||
@@ -125,6 +164,7 @@ void MDNSComponent::dump_config() {
 | 
			
		||||
                "mDNS:\n"
 | 
			
		||||
                "  Hostname: %s",
 | 
			
		||||
                this->hostname_.c_str());
 | 
			
		||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
 | 
			
		||||
  ESP_LOGV(TAG, "  Services:");
 | 
			
		||||
  for (const auto &service : this->services_) {
 | 
			
		||||
    ESP_LOGV(TAG, "  - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(),
 | 
			
		||||
@@ -134,6 +174,7 @@ void MDNSComponent::dump_config() {
 | 
			
		||||
               const_cast<TemplatableValue<std::string> &>(record.value).value().c_str());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<MDNSService> MDNSComponent::get_services() { return this->services_; }
 | 
			
		||||
 
 | 
			
		||||
@@ -201,7 +201,7 @@ def _validate_manifest_version(manifest_data):
 | 
			
		||||
        else:
 | 
			
		||||
            raise cv.Invalid("Invalid manifest version")
 | 
			
		||||
    else:
 | 
			
		||||
        raise cv.Invalid("Invalid manifest file, missing 'version' key.")
 | 
			
		||||
        raise cv.Invalid("Invalid manifest file, missing 'version' key")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _process_http_source(config):
 | 
			
		||||
@@ -421,7 +421,7 @@ def _feature_step_size_validate(config):
 | 
			
		||||
        if features_step_size is None:
 | 
			
		||||
            features_step_size = model_step_size
 | 
			
		||||
        elif features_step_size != model_step_size:
 | 
			
		||||
            raise cv.Invalid("Cannot load models with different features step sizes.")
 | 
			
		||||
            raise cv.Invalid("Cannot load models with different features step sizes")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = cv.All(
 | 
			
		||||
 
 | 
			
		||||
@@ -309,8 +309,12 @@ class DriverChip:
 | 
			
		||||
                CONF_NATIVE_HEIGHT, height + offset_height * 2
 | 
			
		||||
            )
 | 
			
		||||
            offset_height = native_height - height - offset_height
 | 
			
		||||
        # Swap default dimensions if swap_xy is set
 | 
			
		||||
        if transform[CONF_SWAP_XY] is True:
 | 
			
		||||
        # 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 (
 | 
			
		||||
            90,
 | 
			
		||||
            270,
 | 
			
		||||
        )
 | 
			
		||||
        if transform[CONF_SWAP_XY] is True or rotated:
 | 
			
		||||
            width, height = height, width
 | 
			
		||||
            offset_height, offset_width = offset_width, offset_height
 | 
			
		||||
        return width, height, offset_width, offset_height
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user