mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			178 Commits
		
	
	
		
			2021.10.3
			...
			2021.11.0b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					41a3a17456 | ||
| 
						 | 
					cbbafbcca2 | ||
| 
						 | 
					c75566b374 | ||
| 
						 | 
					7279f1fcc1 | ||
| 
						 | 
					d7432f7c10 | ||
| 
						 | 
					b0a0a153f3 | ||
| 
						 | 
					024632dbd0 | ||
| 
						 | 
					0a545a28b9 | ||
| 
						 | 
					0f2df59998 | ||
| 
						 | 
					29a7d32f77 | ||
| 
						 | 
					687a7e9b2f | ||
| 
						 | 
					09e8782318 | ||
| 
						 | 
					f2aea02210 | ||
| 
						 | 
					194f922312 | ||
| 
						 | 
					fea3c48098 | ||
| 
						 | 
					c2f57baec2 | ||
| 
						 | 
					f4a140e126 | ||
| 
						 | 
					ab506b09fe | ||
| 
						 | 
					87e1cdeedb | ||
| 
						 | 
					81a36146ef | ||
| 
						 | 
					7fa4a68a27 | ||
| 
						 | 
					f1c5e2ef81 | ||
| 
						 | 
					b526155cce | ||
| 
						 | 
					62c3f301e7 | ||
| 
						 | 
					38cb988809 | ||
| 
						 | 
					b976ac54c8 | ||
| 
						 | 
					78026e766f | ||
| 
						 | 
					b4cd8d21a5 | ||
| 
						 | 
					7552893311 | ||
| 
						 | 
					21c896d8f8 | ||
| 
						 | 
					4b7fe202ec | ||
| 
						 | 
					9f4519210f | ||
| 
						 | 
					b0506afa5b | ||
| 
						 | 
					8cbb379898 | ||
| 
						 | 
					b226215593 | ||
| 
						 | 
					19970729a9 | ||
| 
						 | 
					f310cacd41 | ||
| 
						 | 
					5ff7c8418c | ||
| 
						 | 
					0bdb48bcac | ||
| 
						 | 
					99c775d8cb | ||
| 
						 | 
					4d43396835 | ||
| 
						 | 
					92321e219a | ||
| 
						 | 
					c422b2fb0b | ||
| 
						 | 
					8aa72f4c1e | ||
| 
						 | 
					d8e33c5a69 | ||
| 
						 | 
					15f9677d33 | ||
| 
						 | 
					219b225ac0 | ||
| 
						 | 
					2ac232e634 | ||
| 
						 | 
					710866ff4e | ||
| 
						 | 
					662773b075 | ||
| 
						 | 
					875b803483 | ||
| 
						 | 
					6e5cfac927 | ||
| 
						 | 
					97eaf3d4a1 | ||
| 
						 | 
					366552a969 | ||
| 
						 | 
					57b07441a1 | ||
| 
						 | 
					fb57ab0add | ||
| 
						 | 
					d6717c0032 | ||
| 
						 | 
					f72389147d | ||
| 
						 | 
					a509f6ccd2 | ||
| 
						 | 
					add484a2ea | ||
| 
						 | 
					a17a6d5346 | ||
| 
						 | 
					be9439f10d | ||
| 
						 | 
					96a50f5c6b | ||
| 
						 | 
					3c0414c420 | ||
| 
						 | 
					b450d4c734 | ||
| 
						 | 
					d536509a63 | ||
| 
						 | 
					11f1e28139 | ||
| 
						 | 
					379c3e98f5 | ||
| 
						 | 
					5ea77894b7 | ||
| 
						 | 
					d54b4e7c44 | ||
| 
						 | 
					d8b3af3815 | ||
| 
						 | 
					2b04152482 | ||
| 
						 | 
					331a3ac387 | ||
| 
						 | 
					7eee3cdc7f | ||
| 
						 | 
					b12c7432e0 | ||
| 
						 | 
					696643d037 | ||
| 
						 | 
					7e54f97003 | ||
| 
						 | 
					77dbf84e55 | ||
| 
						 | 
					2350c5054c | ||
| 
						 | 
					73accf747f | ||
| 
						 | 
					0d3e6b2c4c | ||
| 
						 | 
					b3d7cc637b | ||
| 
						 | 
					2147bcbc29 | ||
| 
						 | 
					980c2d4cae | ||
| 
						 | 
					68316cbcf9 | ||
| 
						 | 
					2f4b9263c3 | ||
| 
						 | 
					c2623a08e3 | ||
| 
						 | 
					9f625ee7d1 | ||
| 
						 | 
					2f85c27a05 | ||
| 
						 | 
					c612a3bf60 | ||
| 
						 | 
					a01f5f5cf1 | ||
| 
						 | 
					87328686a0 | ||
| 
						 | 
					81c11ba1f7 | ||
| 
						 | 
					49b17c5a2d | ||
| 
						 | 
					de06a781ff | ||
| 
						 | 
					8e77e3c685 | ||
| 
						 | 
					a687b083ae | ||
| 
						 | 
					b9e5c7eb35 | ||
| 
						 | 
					1a6a063e04 | ||
| 
						 | 
					d85b7a6bd0 | ||
| 
						 | 
					1c4700f447 | ||
| 
						 | 
					83400d0417 | ||
| 
						 | 
					77a6461c9d | ||
| 
						 | 
					6db9d1122f | ||
| 
						 | 
					83bef85415 | ||
| 
						 | 
					b5b3914bbf | ||
| 
						 | 
					0d90ef94ae | ||
| 
						 | 
					c08b21b7cd | ||
| 
						 | 
					be3cb9ef00 | ||
| 
						 | 
					f7b3f52731 | ||
| 
						 | 
					9220d9fc52 | ||
| 
						 | 
					68c8547067 | ||
| 
						 | 
					1468acfced | ||
| 
						 | 
					b141aea4c0 | ||
| 
						 | 
					a88c022406 | ||
| 
						 | 
					5389382798 | ||
| 
						 | 
					4765173778 | ||
| 
						 | 
					07a9cb910f | ||
| 
						 | 
					f408f074c4 | ||
| 
						 | 
					f1f2640d0e | ||
| 
						 | 
					27d7d7ca69 | ||
| 
						 | 
					c0fc5b48ae | ||
| 
						 | 
					8735d3b83e | ||
| 
						 | 
					ca59dd1302 | ||
| 
						 | 
					eccdef8211 | ||
| 
						 | 
					f2ebfe7aef | ||
| 
						 | 
					cac5b356db | ||
| 
						 | 
					156104d5f5 | ||
| 
						 | 
					c248ba4043 | ||
| 
						 | 
					c615dc573a | ||
| 
						 | 
					1caabb6419 | ||
| 
						 | 
					f41f7994a3 | ||
| 
						 | 
					e39f314e7a | ||
| 
						 | 
					7f34561e53 | ||
| 
						 | 
					34606b0f1f | ||
| 
						 | 
					c51b509501 | ||
| 
						 | 
					15b5968418 | ||
| 
						 | 
					64a45dc6a6 | ||
| 
						 | 
					7cfede5b83 | ||
| 
						 | 
					e4d17e0b15 | ||
| 
						 | 
					e79f7ce290 | ||
| 
						 | 
					bcc77c73e1 | ||
| 
						 | 
					8b11e5aeb1 | ||
| 
						 | 
					cb48394e8a | ||
| 
						 | 
					f5441a87e3 | ||
| 
						 | 
					e2a812fa4b | ||
| 
						 | 
					5b5ead872b | ||
| 
						 | 
					03cfd78c59 | ||
| 
						 | 
					ced11bc707 | ||
| 
						 | 
					6b9c084162 | ||
| 
						 | 
					644ce2a26c | ||
| 
						 | 
					5425e45851 | ||
| 
						 | 
					12fce7a08d | ||
| 
						 | 
					0991ab3543 | ||
| 
						 | 
					65d2b37496 | ||
| 
						 | 
					94d518a418 | ||
| 
						 | 
					7cca673902 | ||
| 
						 | 
					384f8d97d8 | ||
| 
						 | 
					c82d5d63e3 | ||
| 
						 | 
					653a3d5d11 | ||
| 
						 | 
					884b7201de | ||
| 
						 | 
					85d2f24447 | ||
| 
						 | 
					935992bcb3 | ||
| 
						 | 
					dc15d1c8ec | ||
| 
						 | 
					6beb9e568a | ||
| 
						 | 
					7178f10bda | ||
| 
						 | 
					1308236429 | ||
| 
						 | 
					63d6b610b8 | ||
| 
						 | 
					8823024509 | ||
| 
						 | 
					4896f870f0 | ||
| 
						 | 
					7e482901d9 | ||
| 
						 | 
					07b309e65d | ||
| 
						 | 
					6bbb5e9b56 | ||
| 
						 | 
					867fecd157 | ||
| 
						 | 
					05388d2dfc | ||
| 
						 | 
					e06b6d7140 | ||
| 
						 | 
					859e508392 | ||
| 
						 | 
					534ce11d54 | 
							
								
								
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -16,6 +16,7 @@ Quick description and explanation of changes
 | 
			
		||||
## Test Environment
 | 
			
		||||
 | 
			
		||||
- [ ] ESP32
 | 
			
		||||
- [ ] ESP32 IDF
 | 
			
		||||
- [ ] ESP8266
 | 
			
		||||
 | 
			
		||||
## Example entry for `config.yaml`:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								.github/issue-close-app.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/issue-close-app.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +0,0 @@
 | 
			
		||||
comment: >-
 | 
			
		||||
  https://github.com/esphome/esphome/issues/430
 | 
			
		||||
issueConfigs:
 | 
			
		||||
- content:
 | 
			
		||||
  - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
 | 
			
		||||
 | 
			
		||||
caseInsensitive: false
 | 
			
		||||
							
								
								
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								.github/lock.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,36 +0,0 @@
 | 
			
		||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
 | 
			
		||||
 | 
			
		||||
# Number of days of inactivity before a closed issue or pull request is locked
 | 
			
		||||
daysUntilLock: 7
 | 
			
		||||
 | 
			
		||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
 | 
			
		||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
 | 
			
		||||
skipCreatedBefore: false
 | 
			
		||||
 | 
			
		||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
 | 
			
		||||
exemptLabels:
 | 
			
		||||
  - keep-open
 | 
			
		||||
 | 
			
		||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
 | 
			
		||||
lockLabel: false
 | 
			
		||||
 | 
			
		||||
# Comment to post before locking. Set to `false` to disable
 | 
			
		||||
lockComment: false
 | 
			
		||||
 | 
			
		||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
 | 
			
		||||
setLockReason: false
 | 
			
		||||
 | 
			
		||||
# Limit to only `issues` or `pulls`
 | 
			
		||||
# only: issues
 | 
			
		||||
 | 
			
		||||
# Optionally, specify configuration settings just for `issues` or `pulls`
 | 
			
		||||
# issues:
 | 
			
		||||
#   exemptLabels:
 | 
			
		||||
#     - help-wanted
 | 
			
		||||
#   lockLabel: outdated
 | 
			
		||||
 | 
			
		||||
# pulls:
 | 
			
		||||
#   daysUntilLock: 30
 | 
			
		||||
 | 
			
		||||
# Repository to extend settings from
 | 
			
		||||
# _extends: repo
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -17,6 +17,10 @@ on:
 | 
			
		||||
      - 'requirements*.txt'
 | 
			
		||||
      - 'platformio.ini'
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
  packages: read
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check-docker:
 | 
			
		||||
    name: Build docker containers
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -8,6 +8,9 @@ on:
 | 
			
		||||
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  ci:
 | 
			
		||||
    name: ${{ matrix.name }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,13 +9,19 @@ permissions:
 | 
			
		||||
  issues: write
 | 
			
		||||
  pull-requests: write
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: lock
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lock:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: dessant/lock-threads@v2
 | 
			
		||||
      - uses: dessant/lock-threads@v3
 | 
			
		||||
        with:
 | 
			
		||||
          github-token: ${{ github.token }}
 | 
			
		||||
          pr-lock-inactive-days: "1"
 | 
			
		||||
          pr-inactive-days: "1"
 | 
			
		||||
          pr-lock-reason: ""
 | 
			
		||||
          process-only: prs
 | 
			
		||||
          exclude-any-pr-labels: keep-open
 | 
			
		||||
 | 
			
		||||
          issue-inactive-days: "7"
 | 
			
		||||
          issue-lock-reason: ""
 | 
			
		||||
          exclude-any-issue-labels: keep-open
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,6 +7,9 @@ on:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: "0 2 * * *"
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  init:
 | 
			
		||||
    name: Initialize build
 | 
			
		||||
@@ -52,6 +55,9 @@ jobs:
 | 
			
		||||
  deploy-docker:
 | 
			
		||||
    name: Build and publish docker containers
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [init]
 | 
			
		||||
    strategy:
 | 
			
		||||
@@ -93,6 +99,9 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  deploy-docker-manifest:
 | 
			
		||||
    if: github.repository == 'esphome/esphome'
 | 
			
		||||
    permissions:
 | 
			
		||||
      contents: read
 | 
			
		||||
      packages: write
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: [init, deploy-docker]
 | 
			
		||||
    strategy:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,13 +9,15 @@ permissions:
 | 
			
		||||
  issues: write
 | 
			
		||||
  pull-requests: write
 | 
			
		||||
 | 
			
		||||
concurrency:
 | 
			
		||||
  group: lock
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  stale:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v4
 | 
			
		||||
        with:
 | 
			
		||||
          repo-token: ${{ github.token }}
 | 
			
		||||
          days-before-pr-stale: 90
 | 
			
		||||
          days-before-pr-close: 7
 | 
			
		||||
          days-before-issue-stale: -1
 | 
			
		||||
@@ -28,3 +30,19 @@ jobs:
 | 
			
		||||
            pull request has been automatically marked as stale because of that
 | 
			
		||||
            and will be closed if no further activity occurs within 7 days.
 | 
			
		||||
            Thank you for your contributions.
 | 
			
		||||
 | 
			
		||||
  # Use stale to automatically close issues with a reference to the issue tracker
 | 
			
		||||
  close-issues:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/stale@v4
 | 
			
		||||
        with:
 | 
			
		||||
          days-before-pr-stale: -1
 | 
			
		||||
          days-before-pr-close: -1
 | 
			
		||||
          days-before-issue-stale: 1
 | 
			
		||||
          days-before-issue-close: 1
 | 
			
		||||
          remove-stale-when-updated: true
 | 
			
		||||
          stale-issue-label: "stale"
 | 
			
		||||
          exempt-issue-labels: "not-stale"
 | 
			
		||||
          stale-issue-message: >
 | 
			
		||||
            https://github.com/esphome/esphome/issues/430
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core
 | 
			
		||||
esphome/components/ble_client/* @buxtronix
 | 
			
		||||
esphome/components/bme680_bsec/* @trvrnrth
 | 
			
		||||
esphome/components/canbus/* @danielschramm @mvturnho
 | 
			
		||||
esphome/components/cap1188/* @MrEditor97
 | 
			
		||||
esphome/components/captive_portal/* @OttoWinter
 | 
			
		||||
esphome/components/ccs811/* @habbie
 | 
			
		||||
esphome/components/climate/* @esphome/core
 | 
			
		||||
@@ -39,6 +40,7 @@ esphome/components/color_temperature/* @jesserockz
 | 
			
		||||
esphome/components/coolix/* @glmnet
 | 
			
		||||
esphome/components/cover/* @esphome/core
 | 
			
		||||
esphome/components/cs5460a/* @balrog-kun
 | 
			
		||||
esphome/components/cse7761/* @berfenger
 | 
			
		||||
esphome/components/ct_clamp/* @jesserockz
 | 
			
		||||
esphome/components/current_based/* @djwmarcx
 | 
			
		||||
esphome/components/daly_bms/* @s1lvi0
 | 
			
		||||
@@ -51,6 +53,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk
 | 
			
		||||
esphome/components/esp32/* @esphome/core
 | 
			
		||||
esphome/components/esp32_ble/* @jesserockz
 | 
			
		||||
esphome/components/esp32_ble_server/* @jesserockz
 | 
			
		||||
esphome/components/esp32_camera_web_server/* @ayufan
 | 
			
		||||
esphome/components/esp32_improv/* @jesserockz
 | 
			
		||||
esphome/components/esp8266/* @esphome/core
 | 
			
		||||
esphome/components/exposure_notifications/* @OttoWinter
 | 
			
		||||
@@ -70,6 +73,7 @@ esphome/components/homeassistant/* @OttoWinter
 | 
			
		||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
 | 
			
		||||
esphome/components/i2c/* @esphome/core
 | 
			
		||||
esphome/components/improv/* @jesserockz
 | 
			
		||||
esphome/components/improv_serial/* @esphome/core
 | 
			
		||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
 | 
			
		||||
esphome/components/inkplate6/* @jesserockz
 | 
			
		||||
esphome/components/integration/* @OttoWinter
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,12 @@
 | 
			
		||||
# One of "docker", "hassio"
 | 
			
		||||
ARG BASEIMGTYPE=docker
 | 
			
		||||
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7
 | 
			
		||||
FROM debian:bullseye-20210902-slim AS base-docker-amd64
 | 
			
		||||
FROM debian:bullseye-20210902-slim AS base-docker-arm64
 | 
			
		||||
FROM debian:bullseye-20210902-slim AS base-docker-armv7
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
 | 
			
		||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
 | 
			
		||||
FROM debian:bullseye-20211011-slim AS base-docker-amd64
 | 
			
		||||
FROM debian:bullseye-20211011-slim AS base-docker-arm64
 | 
			
		||||
FROM debian:bullseye-20211011-slim AS base-docker-armv7
 | 
			
		||||
 | 
			
		||||
# Use TARGETARCH/TARGETVARIANT defined by docker
 | 
			
		||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
 | 
			
		||||
@@ -43,7 +43,7 @@ RUN \
 | 
			
		||||
    # Ubuntu python3-pip is missing wheel
 | 
			
		||||
    pip3 install --no-cache-dir \
 | 
			
		||||
        wheel==0.36.2 \
 | 
			
		||||
        platformio==5.2.1 \
 | 
			
		||||
        platformio==5.2.2 \
 | 
			
		||||
    # Change some platformio settings
 | 
			
		||||
    && platformio settings set enable_telemetry No \
 | 
			
		||||
    && platformio settings set check_libraries_interval 1000000 \
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_AUTOMATION_ID,
 | 
			
		||||
    CONF_CONDITION,
 | 
			
		||||
    CONF_COUNT,
 | 
			
		||||
    CONF_ELSE,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_THEN,
 | 
			
		||||
@@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
 | 
			
		||||
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
 | 
			
		||||
IfAction = cg.esphome_ns.class_("IfAction", Action)
 | 
			
		||||
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
 | 
			
		||||
RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
 | 
			
		||||
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
 | 
			
		||||
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
 | 
			
		||||
Automation = cg.esphome_ns.class_("Automation")
 | 
			
		||||
@@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register_action(
 | 
			
		||||
    "repeat",
 | 
			
		||||
    RepeatAction,
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
 | 
			
		||||
            cv.Required(CONF_THEN): validate_action_list,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
async def repeat_action_to_code(config, action_id, template_arg, args):
 | 
			
		||||
    var = cg.new_Pvariable(action_id, template_arg)
 | 
			
		||||
    count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
 | 
			
		||||
    cg.add(var.set_count(count_template))
 | 
			
		||||
    actions = await build_action_list(config[CONF_THEN], template_arg, args)
 | 
			
		||||
    cg.add(var.add_then(actions))
 | 
			
		||||
    return var
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_wait_until(value):
 | 
			
		||||
    schema = cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -81,4 +81,5 @@ from esphome.cpp_types import (  # noqa
 | 
			
		||||
    GPIOPin,
 | 
			
		||||
    InternalGPIOPin,
 | 
			
		||||
    gpio_Flags,
 | 
			
		||||
    EntityCategory,
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#include "adc_sensor.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
#ifdef USE_ADC_SENSOR_VCC
 | 
			
		||||
@@ -15,50 +16,6 @@ namespace adc {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "adc";
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
 | 
			
		||||
 | 
			
		||||
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
 | 
			
		||||
#if CONFIG_IDF_TARGET_ESP32
 | 
			
		||||
  switch (pin) {
 | 
			
		||||
    case 36:
 | 
			
		||||
      return ADC1_CHANNEL_0;
 | 
			
		||||
    case 37:
 | 
			
		||||
      return ADC1_CHANNEL_1;
 | 
			
		||||
    case 38:
 | 
			
		||||
      return ADC1_CHANNEL_2;
 | 
			
		||||
    case 39:
 | 
			
		||||
      return ADC1_CHANNEL_3;
 | 
			
		||||
    case 32:
 | 
			
		||||
      return ADC1_CHANNEL_4;
 | 
			
		||||
    case 33:
 | 
			
		||||
      return ADC1_CHANNEL_5;
 | 
			
		||||
    case 34:
 | 
			
		||||
      return ADC1_CHANNEL_6;
 | 
			
		||||
    case 35:
 | 
			
		||||
      return ADC1_CHANNEL_7;
 | 
			
		||||
    default:
 | 
			
		||||
      return ADC1_CHANNEL_MAX;
 | 
			
		||||
  }
 | 
			
		||||
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
 | 
			
		||||
  switch (pin) {
 | 
			
		||||
    case 0:
 | 
			
		||||
      return ADC1_CHANNEL_0;
 | 
			
		||||
    case 1:
 | 
			
		||||
      return ADC1_CHANNEL_1;
 | 
			
		||||
    case 2:
 | 
			
		||||
      return ADC1_CHANNEL_2;
 | 
			
		||||
    case 3:
 | 
			
		||||
      return ADC1_CHANNEL_3;
 | 
			
		||||
    case 4:
 | 
			
		||||
      return ADC1_CHANNEL_4;
 | 
			
		||||
    default:
 | 
			
		||||
      return ADC1_CHANNEL_MAX;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void ADCSensor::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
 | 
			
		||||
#ifndef USE_ADC_SENSOR_VCC
 | 
			
		||||
@@ -66,13 +23,36 @@ void ADCSensor::setup() {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
 | 
			
		||||
  adc1_config_width(ADC_WIDTH_BIT_12);
 | 
			
		||||
#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
 | 
			
		||||
  adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin()));
 | 
			
		||||
#endif
 | 
			
		||||
  if (!autorange_) {
 | 
			
		||||
    adc1_config_channel_atten(channel_, attenuation_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // load characteristics for each attenuation
 | 
			
		||||
  for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) {
 | 
			
		||||
    auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12,
 | 
			
		||||
                                              1100,  // default vref
 | 
			
		||||
                                              &cal_characteristics_[i]);
 | 
			
		||||
    switch (cal_value) {
 | 
			
		||||
      case ESP_ADC_CAL_VAL_EFUSE_VREF:
 | 
			
		||||
        ESP_LOGV(TAG, "Using eFuse Vref for calibration");
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_ADC_CAL_VAL_EFUSE_TP:
 | 
			
		||||
        ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
 | 
			
		||||
        break;
 | 
			
		||||
      case ESP_ADC_CAL_VAL_DEFAULT_VREF:
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
 | 
			
		||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
 | 
			
		||||
  adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ADCSensor::dump_config() {
 | 
			
		||||
  LOG_SENSOR("", "ADC Sensor", this);
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
@@ -81,84 +61,107 @@ void ADCSensor::dump_config() {
 | 
			
		||||
#else
 | 
			
		||||
  LOG_PIN("  Pin: ", pin_);
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
#endif  // USE_ESP8266
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  LOG_PIN("  Pin: ", pin_);
 | 
			
		||||
  switch (this->attenuation_) {
 | 
			
		||||
    case ADC_ATTEN_DB_0:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_2_5:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_6:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_11:
 | 
			
		||||
      ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
 | 
			
		||||
      break;
 | 
			
		||||
    default:  // This is to satisfy the unused ADC_ATTEN_MAX
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  if (autorange_)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, " Attenuation: auto");
 | 
			
		||||
  else
 | 
			
		||||
    switch (this->attenuation_) {
 | 
			
		||||
      case ADC_ATTEN_DB_0:
 | 
			
		||||
        ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
 | 
			
		||||
        break;
 | 
			
		||||
      case ADC_ATTEN_DB_2_5:
 | 
			
		||||
        ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
 | 
			
		||||
        break;
 | 
			
		||||
      case ADC_ATTEN_DB_6:
 | 
			
		||||
        ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
 | 
			
		||||
        break;
 | 
			
		||||
      case ADC_ATTEN_DB_11:
 | 
			
		||||
        ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
 | 
			
		||||
        break;
 | 
			
		||||
      default:  // This is to satisfy the unused ADC_ATTEN_MAX
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
void ADCSensor::update() {
 | 
			
		||||
  float value_v = this->sample();
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
 | 
			
		||||
  this->publish_state(value_v);
 | 
			
		||||
}
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin()));
 | 
			
		||||
  float value_v = raw / 4095.0f;
 | 
			
		||||
#if CONFIG_IDF_TARGET_ESP32
 | 
			
		||||
  switch (this->attenuation_) {
 | 
			
		||||
    case ADC_ATTEN_DB_0:
 | 
			
		||||
      value_v *= 1.1;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_2_5:
 | 
			
		||||
      value_v *= 1.5;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_6:
 | 
			
		||||
      value_v *= 2.2;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_11:
 | 
			
		||||
      value_v *= 3.9;
 | 
			
		||||
      break;
 | 
			
		||||
    default:  // This is to satisfy the unused ADC_ATTEN_MAX
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
 | 
			
		||||
  switch (this->attenuation_) {
 | 
			
		||||
    case ADC_ATTEN_DB_0:
 | 
			
		||||
      value_v *= 0.84;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_2_5:
 | 
			
		||||
      value_v *= 1.13;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_6:
 | 
			
		||||
      value_v *= 1.56;
 | 
			
		||||
      break;
 | 
			
		||||
    case ADC_ATTEN_DB_11:
 | 
			
		||||
      value_v *= 3.0;
 | 
			
		||||
      break;
 | 
			
		||||
    default:  // This is to satisfy the unused ADC_ATTEN_MAX
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
  return value_v;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
#ifdef USE_ADC_SENSOR_VCC
 | 
			
		||||
  return ESP.getVcc() / 1024.0f;  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
  int raw = ESP.getVcc();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
#else
 | 
			
		||||
  return analogRead(this->pin_->get_pin()) / 1024.0f;  // NOLINT
 | 
			
		||||
#endif
 | 
			
		||||
  int raw = analogRead(this->pin_->get_pin());  // NOLINT
 | 
			
		||||
#endif
 | 
			
		||||
  if (output_raw_) {
 | 
			
		||||
    return raw;
 | 
			
		||||
  }
 | 
			
		||||
  return raw / 1024.0f;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
float ADCSensor::sample() {
 | 
			
		||||
  if (!autorange_) {
 | 
			
		||||
    int raw = adc1_get_raw(channel_);
 | 
			
		||||
    if (raw == -1) {
 | 
			
		||||
      return NAN;
 | 
			
		||||
    }
 | 
			
		||||
    if (output_raw_) {
 | 
			
		||||
      return raw;
 | 
			
		||||
    }
 | 
			
		||||
    uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
 | 
			
		||||
    return mv / 1000.0f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
 | 
			
		||||
  adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
 | 
			
		||||
  raw11 = adc1_get_raw(channel_);
 | 
			
		||||
  if (raw11 < 4095) {
 | 
			
		||||
    adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
 | 
			
		||||
    raw6 = adc1_get_raw(channel_);
 | 
			
		||||
    if (raw6 < 4095) {
 | 
			
		||||
      adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
 | 
			
		||||
      raw2 = adc1_get_raw(channel_);
 | 
			
		||||
      if (raw2 < 4095) {
 | 
			
		||||
        adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
 | 
			
		||||
        raw0 = adc1_get_raw(channel_);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
 | 
			
		||||
    return NAN;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
 | 
			
		||||
  uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
 | 
			
		||||
  uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
 | 
			
		||||
  uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
 | 
			
		||||
 | 
			
		||||
  // Contribution of each value, in range 0-2048
 | 
			
		||||
  uint32_t c11 = std::min(raw11, 2048);
 | 
			
		||||
  uint32_t c6 = 2048 - std::abs(raw6 - 2048);
 | 
			
		||||
  uint32_t c2 = 2048 - std::abs(raw2 - 2048);
 | 
			
		||||
  uint32_t c0 = std::min(4095 - raw0, 2048);
 | 
			
		||||
  // max theoretical csum value is 2048*4 = 8192
 | 
			
		||||
  uint32_t csum = c11 + c6 + c2 + c0;
 | 
			
		||||
 | 
			
		||||
  // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
 | 
			
		||||
  uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
 | 
			
		||||
  return mv_scaled / (float) (csum * 1000U);
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
#include "driver/adc.h"
 | 
			
		||||
#include <esp_adc_cal.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
@@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
 public:
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  /// Set the attenuation for this pin. Only available on the ESP32.
 | 
			
		||||
  void set_attenuation(adc_atten_t attenuation);
 | 
			
		||||
  void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
 | 
			
		||||
  void set_channel(adc1_channel_t channel) { channel_ = channel; }
 | 
			
		||||
  void set_autorange(bool autorange) { autorange_ = autorange; }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  /// Update adc values.
 | 
			
		||||
@@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
  /// `HARDWARE_LATE` setup priority.
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
 | 
			
		||||
  void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
 | 
			
		||||
  float sample() override;
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
@@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  bool output_raw_{false};
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
  adc_atten_t attenuation_{ADC_ATTEN_DB_0};
 | 
			
		||||
  adc1_channel_t channel_{};
 | 
			
		||||
  bool autorange_{false};
 | 
			
		||||
  esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,24 @@ from esphome import pins
 | 
			
		||||
from esphome.components import sensor, voltage_sampler
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ATTENUATION,
 | 
			
		||||
    CONF_RAW,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_PIN,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import get_esp32_variant
 | 
			
		||||
from esphome.components.esp32.const import (
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
AUTO_LOAD = ["voltage_sampler"]
 | 
			
		||||
@@ -21,6 +31,62 @@ ATTENUATION_MODES = {
 | 
			
		||||
    "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
 | 
			
		||||
    "6db": cg.global_ns.ADC_ATTEN_DB_6,
 | 
			
		||||
    "11db": cg.global_ns.ADC_ATTEN_DB_11,
 | 
			
		||||
    "auto": "auto",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
 | 
			
		||||
 | 
			
		||||
# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
 | 
			
		||||
# pin to adc1 channel mapping
 | 
			
		||||
ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
 | 
			
		||||
    VARIANT_ESP32: {
 | 
			
		||||
        36: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        37: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        38: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        39: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        32: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        33: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        34: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        35: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S2: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32S3: {
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        5: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
        6: adc1_channel_t.ADC1_CHANNEL_5,
 | 
			
		||||
        7: adc1_channel_t.ADC1_CHANNEL_6,
 | 
			
		||||
        8: adc1_channel_t.ADC1_CHANNEL_7,
 | 
			
		||||
        9: adc1_channel_t.ADC1_CHANNEL_8,
 | 
			
		||||
        10: adc1_channel_t.ADC1_CHANNEL_9,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32C3: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
    VARIANT_ESP32H2: {
 | 
			
		||||
        0: adc1_channel_t.ADC1_CHANNEL_0,
 | 
			
		||||
        1: adc1_channel_t.ADC1_CHANNEL_1,
 | 
			
		||||
        2: adc1_channel_t.ADC1_CHANNEL_2,
 | 
			
		||||
        3: adc1_channel_t.ADC1_CHANNEL_3,
 | 
			
		||||
        4: adc1_channel_t.ADC1_CHANNEL_4,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -29,15 +95,16 @@ def validate_adc_pin(value):
 | 
			
		||||
        return cv.only_on_esp8266("VCC")
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        from esphome.components.esp32 import is_esp32c3
 | 
			
		||||
 | 
			
		||||
        value = pins.internal_gpio_input_pin_number(value)
 | 
			
		||||
        if is_esp32c3():
 | 
			
		||||
            if not (0 <= value <= 4):  # ADC1
 | 
			
		||||
                raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.")
 | 
			
		||||
        elif not (32 <= value <= 39):  # ADC1
 | 
			
		||||
            raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
 | 
			
		||||
    elif CORE.is_esp8266:
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
 | 
			
		||||
            raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
 | 
			
		||||
 | 
			
		||||
        if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
 | 
			
		||||
            raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
 | 
			
		||||
        return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        from esphome.components.esp8266.gpio import CONF_ANALOG
 | 
			
		||||
 | 
			
		||||
        value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
 | 
			
		||||
@@ -49,10 +116,14 @@ def validate_adc_pin(value):
 | 
			
		||||
        return pins.gpio_pin_schema(
 | 
			
		||||
            {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
 | 
			
		||||
        )(value)
 | 
			
		||||
    else:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    return pins.internal_gpio_input_pin_schema(value)
 | 
			
		||||
    raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_config(config):
 | 
			
		||||
    if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
 | 
			
		||||
        raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
adc_ns = cg.esphome_ns.namespace("adc")
 | 
			
		||||
@@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_(
 | 
			
		||||
    "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    sensor.sensor_schema(
 | 
			
		||||
        unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
        accuracy_decimals=2,
 | 
			
		||||
@@ -71,12 +142,14 @@ CONFIG_SCHEMA = (
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(ADCSensor),
 | 
			
		||||
            cv.Required(CONF_PIN): validate_adc_pin,
 | 
			
		||||
            cv.Optional(CONF_RAW, default=False): cv.boolean,
 | 
			
		||||
            cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
 | 
			
		||||
                cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(cv.polling_component_schema("60s")),
 | 
			
		||||
    validate_config,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -91,5 +164,17 @@ async def to_code(config):
 | 
			
		||||
        pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
        cg.add(var.set_pin(pin))
 | 
			
		||||
 | 
			
		||||
    if CONF_RAW in config:
 | 
			
		||||
        cg.add(var.set_output_raw(config[CONF_RAW]))
 | 
			
		||||
 | 
			
		||||
    if CONF_ATTENUATION in config:
 | 
			
		||||
        cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
 | 
			
		||||
        if config[CONF_ATTENUATION] == "auto":
 | 
			
		||||
            cg.add(var.set_autorange(cg.global_ns.true))
 | 
			
		||||
        else:
 | 
			
		||||
            cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        variant = get_esp32_variant()
 | 
			
		||||
        pin_num = config[CONF_PIN][CONF_NUMBER]
 | 
			
		||||
        chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
 | 
			
		||||
        cg.add(var.set_channel(chan))
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ void AHT10Component::update() {
 | 
			
		||||
  bool success = false;
 | 
			
		||||
  for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
 | 
			
		||||
    ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
 | 
			
		||||
    delay_microseconds_accurate(4);
 | 
			
		||||
    delayMicroseconds(4);
 | 
			
		||||
 | 
			
		||||
    uint8_t reg = 0;
 | 
			
		||||
    if (this->write(®, 1) != i2c::ERROR_OK) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_BATTERY_LEVEL,
 | 
			
		||||
    ICON_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    CONF_ILLUMINANCE,
 | 
			
		||||
    ICON_BRIGHTNESS_5,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
@@ -20,10 +21,15 @@ CONFIG_SCHEMA = (
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(Am43),
 | 
			
		||||
            cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_BATTERY, 0
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
                device_class=DEVICE_CLASS_BATTERY,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
 | 
			
		||||
                UNIT_PERCENT, ICON_BRIGHTNESS_5, 0
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
                icon=ICON_BRIGHTNESS_5,
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -103,21 +103,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = strtof(this->buf_, nullptr);
 | 
			
		||||
      this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->target_temp_ = ftoc(this->target_temp_);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case SET_TARGET_TEMPERATURE: {
 | 
			
		||||
      this->target_temp_ = strtof(this->buf_, nullptr);
 | 
			
		||||
      this->target_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->target_temp_ = ftoc(this->target_temp_);
 | 
			
		||||
      this->has_target_temp_ = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case READ_CURRENT_TEMPERATURE: {
 | 
			
		||||
      this->current_temp_ = strtof(this->buf_, nullptr);
 | 
			
		||||
      this->current_temp_ = parse_number<float>(this->buf_, sizeof(this->buf_)).value_or(0.0f);
 | 
			
		||||
      if (this->fahrenheit_)
 | 
			
		||||
        this->current_temp_ = ftoc(this->current_temp_);
 | 
			
		||||
      this->has_current_temp_ = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -182,6 +182,8 @@ message DeviceInfoResponse {
 | 
			
		||||
  // The esphome project details if set
 | 
			
		||||
  string project_name = 8;
 | 
			
		||||
  string project_version = 9;
 | 
			
		||||
 | 
			
		||||
  uint32 webserver_port = 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message ListEntitiesRequest {
 | 
			
		||||
@@ -201,6 +203,14 @@ message SubscribeStatesRequest {
 | 
			
		||||
  // Empty
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== COMMON =====================
 | 
			
		||||
 | 
			
		||||
enum EntityCategory {
 | 
			
		||||
  ENTITY_CATEGORY_NONE = 0;
 | 
			
		||||
  ENTITY_CATEGORY_CONFIG = 1;
 | 
			
		||||
  ENTITY_CATEGORY_DIAGNOSTIC = 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ==================== BINARY SENSOR ====================
 | 
			
		||||
message ListEntitiesBinarySensorResponse {
 | 
			
		||||
  option (id) = 12;
 | 
			
		||||
@@ -216,6 +226,7 @@ message ListEntitiesBinarySensorResponse {
 | 
			
		||||
  bool is_status_binary_sensor = 6;
 | 
			
		||||
  bool disabled_by_default = 7;
 | 
			
		||||
  string icon = 8;
 | 
			
		||||
  EntityCategory entity_category = 9;
 | 
			
		||||
}
 | 
			
		||||
message BinarySensorStateResponse {
 | 
			
		||||
  option (id) = 21;
 | 
			
		||||
@@ -247,6 +258,7 @@ message ListEntitiesCoverResponse {
 | 
			
		||||
  string device_class = 8;
 | 
			
		||||
  bool disabled_by_default = 9;
 | 
			
		||||
  string icon = 10;
 | 
			
		||||
  EntityCategory entity_category = 11;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum LegacyCoverState {
 | 
			
		||||
@@ -316,6 +328,7 @@ message ListEntitiesFanResponse {
 | 
			
		||||
  int32 supported_speed_count = 8;
 | 
			
		||||
  bool disabled_by_default = 9;
 | 
			
		||||
  string icon = 10;
 | 
			
		||||
  EntityCategory entity_category = 11;
 | 
			
		||||
}
 | 
			
		||||
enum FanSpeed {
 | 
			
		||||
  FAN_SPEED_LOW = 0;
 | 
			
		||||
@@ -392,6 +405,7 @@ message ListEntitiesLightResponse {
 | 
			
		||||
  repeated string effects = 11;
 | 
			
		||||
  bool disabled_by_default = 13;
 | 
			
		||||
  string icon = 14;
 | 
			
		||||
  EntityCategory entity_category = 15;
 | 
			
		||||
}
 | 
			
		||||
message LightStateResponse {
 | 
			
		||||
  option (id) = 24;
 | 
			
		||||
@@ -480,6 +494,7 @@ message ListEntitiesSensorResponse {
 | 
			
		||||
  // Last reset type removed in 2021.9.0
 | 
			
		||||
  SensorLastResetType legacy_last_reset_type = 11;
 | 
			
		||||
  bool disabled_by_default = 12;
 | 
			
		||||
  EntityCategory entity_category = 13;
 | 
			
		||||
}
 | 
			
		||||
message SensorStateResponse {
 | 
			
		||||
  option (id) = 25;
 | 
			
		||||
@@ -508,6 +523,7 @@ message ListEntitiesSwitchResponse {
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool assumed_state = 6;
 | 
			
		||||
  bool disabled_by_default = 7;
 | 
			
		||||
  EntityCategory entity_category = 8;
 | 
			
		||||
}
 | 
			
		||||
message SwitchStateResponse {
 | 
			
		||||
  option (id) = 26;
 | 
			
		||||
@@ -541,6 +557,7 @@ message ListEntitiesTextSensorResponse {
 | 
			
		||||
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  bool disabled_by_default = 6;
 | 
			
		||||
  EntityCategory entity_category = 7;
 | 
			
		||||
}
 | 
			
		||||
message TextSensorStateResponse {
 | 
			
		||||
  option (id) = 27;
 | 
			
		||||
@@ -701,6 +718,8 @@ message ListEntitiesCameraResponse {
 | 
			
		||||
  string name = 3;
 | 
			
		||||
  string unique_id = 4;
 | 
			
		||||
  bool disabled_by_default = 5;
 | 
			
		||||
  string icon = 6;
 | 
			
		||||
  EntityCategory entity_category = 7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
message CameraImageResponse {
 | 
			
		||||
@@ -795,6 +814,7 @@ message ListEntitiesClimateResponse {
 | 
			
		||||
  repeated string supported_custom_presets = 17;
 | 
			
		||||
  bool disabled_by_default = 18;
 | 
			
		||||
  string icon = 19;
 | 
			
		||||
  EntityCategory entity_category = 20;
 | 
			
		||||
}
 | 
			
		||||
message ClimateStateResponse {
 | 
			
		||||
  option (id) = 47;
 | 
			
		||||
@@ -863,6 +883,7 @@ message ListEntitiesNumberResponse {
 | 
			
		||||
  float max_value = 7;
 | 
			
		||||
  float step = 8;
 | 
			
		||||
  bool disabled_by_default = 9;
 | 
			
		||||
  EntityCategory entity_category = 10;
 | 
			
		||||
}
 | 
			
		||||
message NumberStateResponse {
 | 
			
		||||
  option (id) = 50;
 | 
			
		||||
@@ -900,6 +921,7 @@ message ListEntitiesSelectResponse {
 | 
			
		||||
  string icon = 5;
 | 
			
		||||
  repeated string options = 6;
 | 
			
		||||
  bool disabled_by_default = 7;
 | 
			
		||||
  EntityCategory entity_category = 8;
 | 
			
		||||
}
 | 
			
		||||
message SelectStateResponse {
 | 
			
		||||
  option (id) = 53;
 | 
			
		||||
 
 | 
			
		||||
@@ -184,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
 | 
			
		||||
  msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
 | 
			
		||||
  msg.disabled_by_default = binary_sensor->is_disabled_by_default();
 | 
			
		||||
  msg.icon = binary_sensor->get_icon();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(binary_sensor->get_entity_category());
 | 
			
		||||
  return this->send_list_entities_binary_sensor_response(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -217,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
 | 
			
		||||
  msg.device_class = cover->get_device_class();
 | 
			
		||||
  msg.disabled_by_default = cover->is_disabled_by_default();
 | 
			
		||||
  msg.icon = cover->get_icon();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(cover->get_entity_category());
 | 
			
		||||
  return this->send_list_entities_cover_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
 | 
			
		||||
@@ -283,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
 | 
			
		||||
  msg.supported_speed_count = traits.supported_speed_count();
 | 
			
		||||
  msg.disabled_by_default = fan->is_disabled_by_default();
 | 
			
		||||
  msg.icon = fan->get_icon();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(fan->get_entity_category());
 | 
			
		||||
  return this->send_list_entities_fan_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
 | 
			
		||||
@@ -346,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
 | 
			
		||||
 | 
			
		||||
  msg.disabled_by_default = light->is_disabled_by_default();
 | 
			
		||||
  msg.icon = light->get_icon();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(light->get_entity_category());
 | 
			
		||||
 | 
			
		||||
  for (auto mode : traits.get_supported_color_modes())
 | 
			
		||||
    msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
 | 
			
		||||
@@ -432,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
 | 
			
		||||
  msg.device_class = sensor->get_device_class();
 | 
			
		||||
  msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
 | 
			
		||||
  msg.disabled_by_default = sensor->is_disabled_by_default();
 | 
			
		||||
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(sensor->get_entity_category());
 | 
			
		||||
  return this->send_list_entities_sensor_response(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -456,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
 | 
			
		||||
  msg.icon = a_switch->get_icon();
 | 
			
		||||
  msg.assumed_state = a_switch->assumed_state();
 | 
			
		||||
  msg.disabled_by_default = a_switch->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
 | 
			
		||||
  return this->send_list_entities_switch_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
 | 
			
		||||
@@ -491,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
 | 
			
		||||
    msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
 | 
			
		||||
  msg.icon = text_sensor->get_icon();
 | 
			
		||||
  msg.disabled_by_default = text_sensor->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
 | 
			
		||||
  return this->send_list_entities_text_sensor_response(msg);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -537,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
 | 
			
		||||
 | 
			
		||||
  msg.disabled_by_default = climate->is_disabled_by_default();
 | 
			
		||||
  msg.icon = climate->get_icon();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(climate->get_entity_category());
 | 
			
		||||
 | 
			
		||||
  msg.supports_current_temperature = traits.get_supports_current_temperature();
 | 
			
		||||
  msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
 | 
			
		||||
@@ -611,6 +618,7 @@ bool APIConnection::send_number_info(number::Number *number) {
 | 
			
		||||
  msg.unique_id = get_default_unique_id("number", number);
 | 
			
		||||
  msg.icon = number->get_icon();
 | 
			
		||||
  msg.disabled_by_default = number->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category());
 | 
			
		||||
 | 
			
		||||
  msg.min_value = number->traits.get_min_value();
 | 
			
		||||
  msg.max_value = number->traits.get_max_value();
 | 
			
		||||
@@ -648,6 +656,7 @@ bool APIConnection::send_select_info(select::Select *select) {
 | 
			
		||||
  msg.unique_id = get_default_unique_id("select", select);
 | 
			
		||||
  msg.icon = select->get_icon();
 | 
			
		||||
  msg.disabled_by_default = select->is_disabled_by_default();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(select->get_entity_category());
 | 
			
		||||
 | 
			
		||||
  for (const auto &option : select->traits.get_options())
 | 
			
		||||
    msg.options.push_back(option);
 | 
			
		||||
@@ -680,6 +689,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
 | 
			
		||||
  msg.name = camera->get_name();
 | 
			
		||||
  msg.unique_id = get_default_unique_id("camera", camera);
 | 
			
		||||
  msg.disabled_by_default = camera->is_disabled_by_default();
 | 
			
		||||
  msg.icon = camera->get_icon();
 | 
			
		||||
  msg.entity_category = static_cast<enums::EntityCategory>(camera->get_entity_category());
 | 
			
		||||
  return this->send_list_entities_camera_response(msg);
 | 
			
		||||
}
 | 
			
		||||
void APIConnection::camera_image(const CameraImageRequest &msg) {
 | 
			
		||||
@@ -758,6 +769,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
 | 
			
		||||
#ifdef ESPHOME_PROJECT_NAME
 | 
			
		||||
  resp.project_name = ESPHOME_PROJECT_NAME;
 | 
			
		||||
  resp.project_version = ESPHOME_PROJECT_VERSION;
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_WEBSERVER
 | 
			
		||||
  resp.webserver_port = WEBSERVER_PORT;
 | 
			
		||||
#endif
 | 
			
		||||
  return resp;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,18 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::ENTITY_CATEGORY_NONE:
 | 
			
		||||
      return "ENTITY_CATEGORY_NONE";
 | 
			
		||||
    case enums::ENTITY_CATEGORY_CONFIG:
 | 
			
		||||
      return "ENTITY_CATEGORY_CONFIG";
 | 
			
		||||
    case enums::ENTITY_CATEGORY_DIAGNOSTIC:
 | 
			
		||||
      return "ENTITY_CATEGORY_DIAGNOSTIC";
 | 
			
		||||
    default:
 | 
			
		||||
      return "UNKNOWN";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
template<> const char *proto_enum_to_string<enums::LegacyCoverState>(enums::LegacyCoverState value) {
 | 
			
		||||
  switch (value) {
 | 
			
		||||
    case enums::LEGACY_COVER_STATE_OPEN:
 | 
			
		||||
@@ -396,6 +408,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
 | 
			
		||||
      this->has_deep_sleep = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->webserver_port = value.as_uint32();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -444,6 +460,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(7, this->has_deep_sleep);
 | 
			
		||||
  buffer.encode_string(8, this->project_name);
 | 
			
		||||
  buffer.encode_string(9, this->project_version);
 | 
			
		||||
  buffer.encode_uint32(10, this->webserver_port);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -484,6 +501,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  project_version: ");
 | 
			
		||||
  out.append("'").append(this->project_version).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  webserver_port: ");
 | 
			
		||||
  sprintf(buffer, "%u", this->webserver_port);
 | 
			
		||||
  out.append(buffer);
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -509,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 9: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -558,6 +584,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_bool(6, this->is_status_binary_sensor);
 | 
			
		||||
  buffer.encode_bool(7, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(8, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -595,6 +622,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -664,6 +695,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -715,6 +750,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(8, this->device_class);
 | 
			
		||||
  buffer.encode_bool(9, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(10, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -760,6 +796,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -948,6 +988,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 11: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -995,6 +1039,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_int32(8, this->supported_speed_count);
 | 
			
		||||
  buffer.encode_bool(9, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(10, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -1041,6 +1086,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -1267,6 +1316,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 15: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -1334,6 +1387,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_bool(13, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(14, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -1401,6 +1455,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -1860,6 +1918,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 13: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -1917,6 +1979,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
 | 
			
		||||
  buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
 | 
			
		||||
  buffer.encode_bool(12, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -1971,6 +2034,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -2033,6 +2100,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -2077,6 +2148,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_bool(6, this->assumed_state);
 | 
			
		||||
  buffer.encode_bool(7, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -2110,6 +2182,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -2197,6 +2273,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -2240,6 +2320,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_string(5, this->icon);
 | 
			
		||||
  buffer.encode_bool(6, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -2269,6 +2350,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -2892,6 +2977,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 7: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -2910,6 +2999,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
 | 
			
		||||
      this->unique_id = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 6: {
 | 
			
		||||
      this->icon = value.as_string();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -2930,6 +3023,8 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_string(3, this->name);
 | 
			
		||||
  buffer.encode_string(4, this->unique_id);
 | 
			
		||||
  buffer.encode_bool(5, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(6, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -2955,6 +3050,14 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -3082,6 +3185,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 20: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -3170,6 +3277,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_bool(18, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_string(19, this->icon);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(20, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -3266,6 +3374,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  icon: ");
 | 
			
		||||
  out.append("'").append(this->icon).append("'");
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -3642,6 +3754,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 10: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -3700,6 +3816,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
  buffer.encode_float(7, this->max_value);
 | 
			
		||||
  buffer.encode_float(8, this->step);
 | 
			
		||||
  buffer.encode_bool(9, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -3744,6 +3861,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@@ -3836,6 +3957,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
 | 
			
		||||
      this->disabled_by_default = value.as_bool();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case 8: {
 | 
			
		||||
      this->entity_category = value.as_enum<enums::EntityCategory>();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -3886,6 +4011,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
 | 
			
		||||
    buffer.encode_string(6, it, true);
 | 
			
		||||
  }
 | 
			
		||||
  buffer.encode_bool(7, this->disabled_by_default);
 | 
			
		||||
  buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
 | 
			
		||||
}
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
 | 
			
		||||
@@ -3921,6 +4047,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
 | 
			
		||||
  out.append("  disabled_by_default: ");
 | 
			
		||||
  out.append(YESNO(this->disabled_by_default));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
 | 
			
		||||
  out.append("  entity_category: ");
 | 
			
		||||
  out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
 | 
			
		||||
  out.append("\n");
 | 
			
		||||
  out.append("}");
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,11 @@ namespace api {
 | 
			
		||||
 | 
			
		||||
namespace enums {
 | 
			
		||||
 | 
			
		||||
enum EntityCategory : uint32_t {
 | 
			
		||||
  ENTITY_CATEGORY_NONE = 0,
 | 
			
		||||
  ENTITY_CATEGORY_CONFIG = 1,
 | 
			
		||||
  ENTITY_CATEGORY_DIAGNOSTIC = 2,
 | 
			
		||||
};
 | 
			
		||||
enum LegacyCoverState : uint32_t {
 | 
			
		||||
  LEGACY_COVER_STATE_OPEN = 0,
 | 
			
		||||
  LEGACY_COVER_STATE_CLOSED = 1,
 | 
			
		||||
@@ -224,6 +229,7 @@ class DeviceInfoResponse : public ProtoMessage {
 | 
			
		||||
  bool has_deep_sleep{false};
 | 
			
		||||
  std::string project_name{};
 | 
			
		||||
  std::string project_version{};
 | 
			
		||||
  uint32_t webserver_port{0};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -270,6 +276,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
 | 
			
		||||
  bool is_status_binary_sensor{false};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -306,6 +313,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
 | 
			
		||||
  std::string device_class{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -363,6 +371,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
 | 
			
		||||
  int32_t supported_speed_count{0};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -428,6 +437,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
 | 
			
		||||
  std::vector<std::string> effects{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -516,6 +526,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
 | 
			
		||||
  enums::SensorStateClass state_class{};
 | 
			
		||||
  enums::SensorLastResetType legacy_last_reset_type{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -549,6 +560,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  bool assumed_state{false};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -593,6 +605,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -803,6 +816,8 @@ class ListEntitiesCameraResponse : public ProtoMessage {
 | 
			
		||||
  std::string name{};
 | 
			
		||||
  std::string unique_id{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -861,6 +876,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
 | 
			
		||||
  std::vector<std::string> supported_custom_presets{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -940,6 +956,7 @@ class ListEntitiesNumberResponse : public ProtoMessage {
 | 
			
		||||
  float max_value{0.0f};
 | 
			
		||||
  float step{0.0f};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
@@ -985,6 +1002,7 @@ class ListEntitiesSelectResponse : public ProtoMessage {
 | 
			
		||||
  std::string icon{};
 | 
			
		||||
  std::vector<std::string> options{};
 | 
			
		||||
  bool disabled_by_default{false};
 | 
			
		||||
  enums::EntityCategory entity_category{};
 | 
			
		||||
  void encode(ProtoWriteBuffer buffer) const override;
 | 
			
		||||
#ifdef HAS_PROTO_MESSAGE_DUMP
 | 
			
		||||
  void dump_to(std::string &out) const override;
 | 
			
		||||
 
 | 
			
		||||
@@ -77,7 +77,7 @@ void APIServer::setup() {
 | 
			
		||||
  this->last_connected_ = millis();
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32_CAMERA
 | 
			
		||||
  if (esp32_camera::global_esp32_camera != nullptr) {
 | 
			
		||||
  if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
 | 
			
		||||
    esp32_camera::global_esp32_camera->add_image_callback(
 | 
			
		||||
        [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
 | 
			
		||||
          for (auto &c : this->clients_)
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ async def async_run_logs(config, address):
 | 
			
		||||
    _LOGGER.info("Starting log output from %s using esphome API", address)
 | 
			
		||||
    zc = zeroconf.Zeroconf()
 | 
			
		||||
    cli = APIClient(
 | 
			
		||||
        asyncio.get_event_loop(),
 | 
			
		||||
        address,
 | 
			
		||||
        port,
 | 
			
		||||
        password,
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,18 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace api {
 | 
			
		||||
 | 
			
		||||
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
 | 
			
		||||
 public:
 | 
			
		||||
  TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
 | 
			
		||||
 | 
			
		||||
  template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
 | 
			
		||||
  TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
 | 
			
		||||
 | 
			
		||||
  template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
 | 
			
		||||
  TemplatableStringValue(F f)
 | 
			
		||||
      : TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename... Ts> class TemplatableKeyValuePair {
 | 
			
		||||
 public:
 | 
			
		||||
  template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
 | 
			
		||||
@@ -19,7 +31,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
 | 
			
		||||
 public:
 | 
			
		||||
  explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent), is_event_(is_event) {}
 | 
			
		||||
 | 
			
		||||
  TEMPLATABLE_STRING_VALUE(service);
 | 
			
		||||
  template<typename T> void set_service(T service) { this->service_ = service; }
 | 
			
		||||
 | 
			
		||||
  template<typename T> void add_data(std::string key, T value) {
 | 
			
		||||
    this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
 | 
			
		||||
  }
 | 
			
		||||
@@ -58,6 +71,7 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
 | 
			
		||||
 protected:
 | 
			
		||||
  APIServer *parent_;
 | 
			
		||||
  bool is_event_;
 | 
			
		||||
  TemplatableStringValue<Ts...> service_{};
 | 
			
		||||
  std::vector<TemplatableKeyValuePair<Ts...>> data_;
 | 
			
		||||
  std::vector<TemplatableKeyValuePair<Ts...>> data_template_;
 | 
			
		||||
  std::vector<TemplatableKeyValuePair<Ts...>> variables_;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
@@ -49,12 +50,14 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                device_class=DEVICE_CLASS_BATTERY,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_POWER_FACTOR,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    ICON_LIGHTBULB,
 | 
			
		||||
    ICON_CURRENT_AC,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
@@ -125,6 +126,7 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_ILLUMINANCE,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_LUX,
 | 
			
		||||
@@ -51,6 +52,7 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                accuracy_decimals=3,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_PERCENT,
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() {
 | 
			
		||||
 | 
			
		||||
  float lx = float(raw_value) / 1.2f;
 | 
			
		||||
  lx *= 69.0f / this->measurement_duration_;
 | 
			
		||||
  if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) {
 | 
			
		||||
    lx /= 2.0f;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
 | 
			
		||||
  this->publish_state(lx);
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 
 | 
			
		||||
@@ -44,10 +44,11 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_PRESENCE,
 | 
			
		||||
    DEVICE_CLASS_PROBLEM,
 | 
			
		||||
    DEVICE_CLASS_RUNNING,
 | 
			
		||||
    DEVICE_CLASS_SAFETY,
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
    DEVICE_CLASS_TAMPER,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
)
 | 
			
		||||
@@ -76,10 +77,11 @@ DEVICE_CLASSES = [
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_PRESENCE,
 | 
			
		||||
    DEVICE_CLASS_PROBLEM,
 | 
			
		||||
    DEVICE_CLASS_RUNNING,
 | 
			
		||||
    DEVICE_CLASS_SAFETY,
 | 
			
		||||
    DEVICE_CLASS_SMOKE,
 | 
			
		||||
    DEVICE_CLASS_SOUND,
 | 
			
		||||
    DEVICE_CLASS_UPDATE,
 | 
			
		||||
    DEVICE_CLASS_TAMPER,
 | 
			
		||||
    DEVICE_CLASS_VIBRATION,
 | 
			
		||||
    DEVICE_CLASS_WINDOW,
 | 
			
		||||
]
 | 
			
		||||
@@ -313,6 +315,7 @@ def validate_multi_click_timing(value):
 | 
			
		||||
 | 
			
		||||
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(BinarySensor),
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@trvrnrth"]
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
@@ -62,9 +61,8 @@ async def to_code(config):
 | 
			
		||||
        var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        # Although this component does not use SPI, the BSEC library requires the SPI library
 | 
			
		||||
        cg.add_library("SPI", None)
 | 
			
		||||
    # Although this component does not use SPI, the BSEC library requires the SPI library
 | 
			
		||||
    cg.add_library("SPI", None)
 | 
			
		||||
 | 
			
		||||
    cg.add_define("USE_BSEC")
 | 
			
		||||
    cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								esphome/components/cap1188/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/cap1188/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import i2c
 | 
			
		||||
from esphome.const import CONF_ID, CONF_RESET_PIN
 | 
			
		||||
from esphome import pins
 | 
			
		||||
 | 
			
		||||
CONF_TOUCH_THRESHOLD = "touch_threshold"
 | 
			
		||||
CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches"
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["i2c"]
 | 
			
		||||
AUTO_LOAD = ["binary_sensor", "output"]
 | 
			
		||||
CODEOWNERS = ["@MrEditor97"]
 | 
			
		||||
 | 
			
		||||
cap1188_ns = cg.esphome_ns.namespace("cap1188")
 | 
			
		||||
CONF_CAP1188_ID = "cap1188_id"
 | 
			
		||||
CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice)
 | 
			
		||||
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(CAP1188Component),
 | 
			
		||||
            cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range(
 | 
			
		||||
                min=0x01, max=0x80
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
    .extend(i2c.i2c_device_schema(0x29))
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
 | 
			
		||||
    cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
 | 
			
		||||
 | 
			
		||||
    if CONF_RESET_PIN in config:
 | 
			
		||||
        pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
 | 
			
		||||
        cg.add(var.set_reset_pin(pin))
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await i2c.register_i2c_device(var, config)
 | 
			
		||||
							
								
								
									
										25
									
								
								esphome/components/cap1188/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								esphome/components/cap1188/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import binary_sensor
 | 
			
		||||
from esphome.const import CONF_CHANNEL, CONF_ID
 | 
			
		||||
from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["cap1188"]
 | 
			
		||||
CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CAP1188Channel),
 | 
			
		||||
        cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component),
 | 
			
		||||
        cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await binary_sensor.register_binary_sensor(var, config)
 | 
			
		||||
    hub = await cg.get_variable(config[CONF_CAP1188_ID])
 | 
			
		||||
    cg.add(var.set_channel(config[CONF_CHANNEL]))
 | 
			
		||||
 | 
			
		||||
    cg.add(hub.register_channel(var))
 | 
			
		||||
							
								
								
									
										88
									
								
								esphome/components/cap1188/cap1188.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								esphome/components/cap1188/cap1188.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
#include "cap1188.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cap1188 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "cap1188";
 | 
			
		||||
 | 
			
		||||
void CAP1188Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up CAP1188...");
 | 
			
		||||
 | 
			
		||||
  // Reset device using the reset pin
 | 
			
		||||
  if (this->reset_pin_ != nullptr) {
 | 
			
		||||
    this->reset_pin_->setup();
 | 
			
		||||
    this->reset_pin_->digital_write(false);
 | 
			
		||||
    delay(100);  // NOLINT
 | 
			
		||||
    this->reset_pin_->digital_write(true);
 | 
			
		||||
    delay(100);  // NOLINT
 | 
			
		||||
    this->reset_pin_->digital_write(false);
 | 
			
		||||
    delay(100);  // NOLINT
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Check if CAP1188 is actually connected
 | 
			
		||||
  this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_);
 | 
			
		||||
  this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_);
 | 
			
		||||
  this->read_byte(CAP1188_REVISION, &this->cap1188_revision_);
 | 
			
		||||
 | 
			
		||||
  if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) {
 | 
			
		||||
    this->error_code_ = COMMUNICATION_FAILED;
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set sensitivity
 | 
			
		||||
  uint8_t sensitivity = 0;
 | 
			
		||||
  this->read_byte(CAP1188_SENSITVITY, &sensitivity);
 | 
			
		||||
  sensitivity = sensitivity & 0x0f;
 | 
			
		||||
  this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
 | 
			
		||||
 | 
			
		||||
  // Allow multiple touches
 | 
			
		||||
  this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
 | 
			
		||||
 | 
			
		||||
  // Have LEDs follow touches
 | 
			
		||||
  this->write_byte(CAP1188_LED_LINK, 0xFF);
 | 
			
		||||
 | 
			
		||||
  // Speed up a bit
 | 
			
		||||
  this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CAP1188Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "CAP1188:");
 | 
			
		||||
  LOG_I2C_DEVICE(this);
 | 
			
		||||
  LOG_PIN("  Reset Pin: ", this->reset_pin_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Product ID: 0x%x", this->cap1188_product_id_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Manufacture ID: 0x%x", this->cap1188_manufacture_id_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Revision ID: 0x%x", this->cap1188_revision_);
 | 
			
		||||
 | 
			
		||||
  switch (this->error_code_) {
 | 
			
		||||
    case COMMUNICATION_FAILED:
 | 
			
		||||
      ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188.");
 | 
			
		||||
      break;
 | 
			
		||||
    case NONE:
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CAP1188Component::loop() {
 | 
			
		||||
  uint8_t touched = 0;
 | 
			
		||||
 | 
			
		||||
  this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1);
 | 
			
		||||
 | 
			
		||||
  if (touched) {
 | 
			
		||||
    uint8_t data = 0;
 | 
			
		||||
    this->read_register(CAP1188_MAIN, &data, 1);
 | 
			
		||||
    data = data & ~CAP1188_MAIN_INT;
 | 
			
		||||
 | 
			
		||||
    this->write_register(CAP1188_MAIN, &data, 2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (auto *channel : this->channels_) {
 | 
			
		||||
    channel->process(touched);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace cap1188
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										68
									
								
								esphome/components/cap1188/cap1188.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								esphome/components/cap1188/cap1188.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/components/i2c/i2c.h"
 | 
			
		||||
#include "esphome/components/output/binary_output.h"
 | 
			
		||||
#include "esphome/components/binary_sensor/binary_sensor.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cap1188 {
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
  CAP1188_I2CADDR = 0x29,
 | 
			
		||||
  CAP1188_SENSOR_INPUT_STATUS = 0x3,
 | 
			
		||||
  CAP1188_MULTI_TOUCH = 0x2A,
 | 
			
		||||
  CAP1188_LED_LINK = 0x72,
 | 
			
		||||
  CAP1188_PRODUCT_ID = 0xFD,
 | 
			
		||||
  CAP1188_MANUFACTURE_ID = 0xFE,
 | 
			
		||||
  CAP1188_STAND_BY_CONFIGURATION = 0x41,
 | 
			
		||||
  CAP1188_REVISION = 0xFF,
 | 
			
		||||
  CAP1188_MAIN = 0x00,
 | 
			
		||||
  CAP1188_MAIN_INT = 0x01,
 | 
			
		||||
  CAP1188_LEDPOL = 0x73,
 | 
			
		||||
  CAP1188_INTERUPT_REPEAT = 0x28,
 | 
			
		||||
  CAP1188_SENSITVITY = 0x1f,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CAP1188Channel : public binary_sensor::BinarySensor {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_channel(uint8_t channel) { channel_ = channel; }
 | 
			
		||||
  void process(uint8_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint8_t channel_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CAP1188Component : public Component, public i2c::I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); }
 | 
			
		||||
  void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
 | 
			
		||||
  void set_allow_multiple_touches(bool allow_multiple_touches) {
 | 
			
		||||
    this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80;
 | 
			
		||||
  };
 | 
			
		||||
  void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::DATA; }
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::vector<CAP1188Channel *> channels_{};
 | 
			
		||||
  uint8_t touch_threshold_{0x20};
 | 
			
		||||
  uint8_t allow_multiple_touches_{0x80};
 | 
			
		||||
 | 
			
		||||
  GPIOPin *reset_pin_{nullptr};
 | 
			
		||||
 | 
			
		||||
  uint8_t cap1188_product_id_{0};
 | 
			
		||||
  uint8_t cap1188_manufacture_id_{0};
 | 
			
		||||
  uint8_t cap1188_revision_{0};
 | 
			
		||||
 | 
			
		||||
  enum ErrorCode {
 | 
			
		||||
    NONE = 0,
 | 
			
		||||
    COMMUNICATION_FAILED,
 | 
			
		||||
  } error_code_{NONE};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace cap1188
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -36,3 +36,5 @@ async def to_code(config):
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        cg.add_library("DNSServer", None)
 | 
			
		||||
        cg.add_library("WiFi", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("DNSServer", None)
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
 | 
			
		||||
  ESP_LOGI(TAG, "  SSID='%s'", ssid.c_str());
 | 
			
		||||
  ESP_LOGI(TAG, "  Password=" LOG_SECRET("'%s'"), psk.c_str());
 | 
			
		||||
  wifi::global_wifi_component->save_wifi_sta(ssid, psk);
 | 
			
		||||
  wifi::global_wifi_component->start_scanning();
 | 
			
		||||
  request->redirect("/?save=true");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ void CCS811Component::setup() {
 | 
			
		||||
 | 
			
		||||
  if (this->baseline_.has_value()) {
 | 
			
		||||
    // baseline available, write to sensor
 | 
			
		||||
    this->write_bytes(0x11, decode_uint16(*this->baseline_));
 | 
			
		||||
    this->write_bytes(0x11, decode_value(*this->baseline_));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto hardware_version_data = this->read_bytes<1>(0x21);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								esphome/components/cse7761/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/cse7761/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										244
									
								
								esphome/components/cse7761/cse7761.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								esphome/components/cse7761/cse7761.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,244 @@
 | 
			
		||||
#include "cse7761.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cse7761 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "cse7761";
 | 
			
		||||
 | 
			
		||||
/*********************************************************************************************\
 | 
			
		||||
 * CSE7761 - Energy  (Sonoff Dual R3 Pow v1.x)
 | 
			
		||||
 *
 | 
			
		||||
 * Based on Tasmota source code
 | 
			
		||||
 * See https://github.com/arendst/Tasmota/discussions/10793
 | 
			
		||||
 * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
 | 
			
		||||
\*********************************************************************************************/
 | 
			
		||||
 | 
			
		||||
static const int CSE7761_UREF = 42563;  // RmsUc
 | 
			
		||||
static const int CSE7761_IREF = 52241;  // RmsIAC
 | 
			
		||||
static const int CSE7761_PREF = 44513;  // PowerPAC
 | 
			
		||||
 | 
			
		||||
static const uint8_t CSE7761_REG_SYSCON = 0x00;     // (2) System Control Register (0x0A04)
 | 
			
		||||
static const uint8_t CSE7761_REG_EMUCON = 0x01;     // (2) Metering control register (0x0000)
 | 
			
		||||
static const uint8_t CSE7761_REG_EMUCON2 = 0x13;    // (2) Metering control register 2 (0x0001)
 | 
			
		||||
static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D;  // (2) Pin function output select register (0x3210)
 | 
			
		||||
 | 
			
		||||
static const uint8_t CSE7761_REG_RMSIA = 0x24;      // (3) The effective value of channel A current (0x000000)
 | 
			
		||||
static const uint8_t CSE7761_REG_RMSIB = 0x25;      // (3) The effective value of channel B current (0x000000)
 | 
			
		||||
static const uint8_t CSE7761_REG_RMSU = 0x26;       // (3) Voltage RMS (0x000000)
 | 
			
		||||
static const uint8_t CSE7761_REG_POWERPA = 0x2C;    // (4) Channel A active power, update rate 27.2Hz (0x00000000)
 | 
			
		||||
static const uint8_t CSE7761_REG_POWERPB = 0x2D;    // (4) Channel B active power, update rate 27.2Hz (0x00000000)
 | 
			
		||||
static const uint8_t CSE7761_REG_SYSSTATUS = 0x43;  // (1) System status register
 | 
			
		||||
 | 
			
		||||
static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F;  // (2) Coefficient checksum
 | 
			
		||||
static const uint8_t CSE7761_REG_RMSIAC = 0x70;       // (2) Channel A effective current conversion coefficient
 | 
			
		||||
 | 
			
		||||
static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA;   // Start special command
 | 
			
		||||
static const uint8_t CSE7761_CMD_RESET = 0x96;         // Reset command, after receiving the command, the chip resets
 | 
			
		||||
static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC;   // Close write operation
 | 
			
		||||
static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5;  // Enable write operation
 | 
			
		||||
 | 
			
		||||
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
 | 
			
		||||
 | 
			
		||||
void CSE7761Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
 | 
			
		||||
  this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
 | 
			
		||||
  uint16_t syscon = this->read_(0x00, 2);  // Default 0x0A04
 | 
			
		||||
  if ((0x0A04 == syscon) && this->chip_init_()) {
 | 
			
		||||
    this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
 | 
			
		||||
    ESP_LOGD(TAG, "CSE7761 found");
 | 
			
		||||
    this->data_.ready = true;
 | 
			
		||||
  } else {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CSE7761Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "CSE7761:");
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "Communication with CSE7761 failed!");
 | 
			
		||||
  }
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
  this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
 | 
			
		||||
 | 
			
		||||
void CSE7761Component::update() {
 | 
			
		||||
  if (this->data_.ready) {
 | 
			
		||||
    this->get_data_();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CSE7761Component::write_(uint8_t reg, uint16_t data) {
 | 
			
		||||
  uint8_t buffer[5];
 | 
			
		||||
 | 
			
		||||
  buffer[0] = 0xA5;
 | 
			
		||||
  buffer[1] = reg;
 | 
			
		||||
  uint32_t len = 2;
 | 
			
		||||
  if (data) {
 | 
			
		||||
    if (data < 0xFF) {
 | 
			
		||||
      buffer[2] = data & 0xFF;
 | 
			
		||||
      len = 3;
 | 
			
		||||
    } else {
 | 
			
		||||
      buffer[2] = (data >> 8) & 0xFF;
 | 
			
		||||
      buffer[3] = data & 0xFF;
 | 
			
		||||
      len = 4;
 | 
			
		||||
    }
 | 
			
		||||
    uint8_t crc = 0;
 | 
			
		||||
    for (uint32_t i = 0; i < len; i++) {
 | 
			
		||||
      crc += buffer[i];
 | 
			
		||||
    }
 | 
			
		||||
    buffer[len] = ~crc;
 | 
			
		||||
    len++;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write_array(buffer, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
 | 
			
		||||
  while (this->available()) {
 | 
			
		||||
    this->read();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write_(reg, 0);
 | 
			
		||||
 | 
			
		||||
  uint8_t buffer[8] = {0};
 | 
			
		||||
  uint32_t rcvd = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint32_t i = 0; i <= size; i++) {
 | 
			
		||||
    int value = this->read();
 | 
			
		||||
    if (value > -1 && rcvd < sizeof(buffer) - 1) {
 | 
			
		||||
      buffer[rcvd++] = value;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!rcvd) {
 | 
			
		||||
    ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rcvd--;
 | 
			
		||||
  uint32_t result = 0;
 | 
			
		||||
  // CRC check
 | 
			
		||||
  uint8_t crc = 0xA5 + reg;
 | 
			
		||||
  for (uint32_t i = 0; i < rcvd; i++) {
 | 
			
		||||
    result = (result << 8) | buffer[i];
 | 
			
		||||
    crc += buffer[i];
 | 
			
		||||
  }
 | 
			
		||||
  crc = ~crc;
 | 
			
		||||
  if (crc != buffer[rcvd]) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  *value = result;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
 | 
			
		||||
  bool result = false;  // Start loop
 | 
			
		||||
  uint8_t retry = 3;    // Retry up to three times
 | 
			
		||||
  uint32_t value = 0;   // Default no value
 | 
			
		||||
  while (!result && retry > 0) {
 | 
			
		||||
    retry--;
 | 
			
		||||
    if (this->read_once_(reg, size, &value))
 | 
			
		||||
      return value;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
 | 
			
		||||
  switch (unit) {
 | 
			
		||||
    case RMS_UC:
 | 
			
		||||
      return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
 | 
			
		||||
    case RMS_IAC:
 | 
			
		||||
      return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10;  // Stay within 32 bits
 | 
			
		||||
    case POWER_PAC:
 | 
			
		||||
      return 0x80000000 / this->data_.coefficient[POWER_PAC];
 | 
			
		||||
  }
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CSE7761Component::chip_init_() {
 | 
			
		||||
  uint16_t calc_chksum = 0xFFFF;
 | 
			
		||||
  for (uint32_t i = 0; i < 8; i++) {
 | 
			
		||||
    this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
 | 
			
		||||
    calc_chksum += this->data_.coefficient[i];
 | 
			
		||||
  }
 | 
			
		||||
  calc_chksum = ~calc_chksum;
 | 
			
		||||
  uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
 | 
			
		||||
  if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
 | 
			
		||||
    ESP_LOGD(TAG, "Default calibration");
 | 
			
		||||
    this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
 | 
			
		||||
    this->data_.coefficient[RMS_UC] = CSE7761_UREF;
 | 
			
		||||
    this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
 | 
			
		||||
 | 
			
		||||
  uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
 | 
			
		||||
  if (sys_status & 0x10) {  // Write enable to protected registers (WREN)
 | 
			
		||||
    this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
 | 
			
		||||
    this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
 | 
			
		||||
    this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
 | 
			
		||||
    this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGD(TAG, "Write failed at chip_init");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CSE7761Component::get_data_() {
 | 
			
		||||
  // The effective value of current and voltage Rms is a 24-bit signed number,
 | 
			
		||||
  // the highest bit is 0 for valid data,
 | 
			
		||||
  //   and when the highest bit is 1, the reading will be processed as zero
 | 
			
		||||
  // The active power parameter PowerA/B is in two’s complement format, 32-bit
 | 
			
		||||
  // data, the highest bit is Sign bit.
 | 
			
		||||
  uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
 | 
			
		||||
  this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
 | 
			
		||||
 | 
			
		||||
  value = this->read_(CSE7761_REG_RMSIA, 3);
 | 
			
		||||
  this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value;  // No load threshold of 10mA
 | 
			
		||||
  value = this->read_(CSE7761_REG_POWERPA, 4);
 | 
			
		||||
  this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
 | 
			
		||||
 | 
			
		||||
  value = this->read_(CSE7761_REG_RMSIB, 3);
 | 
			
		||||
  this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value;  // No load threshold of 10mA
 | 
			
		||||
  value = this->read_(CSE7761_REG_POWERPB, 4);
 | 
			
		||||
  this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
 | 
			
		||||
 | 
			
		||||
  // convert values and publish to sensors
 | 
			
		||||
 | 
			
		||||
  float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
 | 
			
		||||
  if (this->voltage_sensor_ != nullptr) {
 | 
			
		||||
    this->voltage_sensor_->publish_state(voltage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (uint32_t channel = 0; channel < 2; channel++) {
 | 
			
		||||
    // Active power = PowerPA * PowerPAC * 1000 / 0x80000000
 | 
			
		||||
    float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC);  // W
 | 
			
		||||
    float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC);             // A
 | 
			
		||||
    ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
 | 
			
		||||
    if (channel == 0) {
 | 
			
		||||
      if (this->power_sensor_1_ != nullptr) {
 | 
			
		||||
        this->power_sensor_1_->publish_state(active_power);
 | 
			
		||||
      }
 | 
			
		||||
      if (this->current_sensor_1_ != nullptr) {
 | 
			
		||||
        this->current_sensor_1_->publish_state(amps);
 | 
			
		||||
      }
 | 
			
		||||
    } else if (channel == 1) {
 | 
			
		||||
      if (this->power_sensor_2_ != nullptr) {
 | 
			
		||||
        this->power_sensor_2_->publish_state(active_power);
 | 
			
		||||
      }
 | 
			
		||||
      if (this->current_sensor_2_ != nullptr) {
 | 
			
		||||
        this->current_sensor_2_->publish_state(amps);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace cse7761
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										52
									
								
								esphome/components/cse7761/cse7761.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								esphome/components/cse7761/cse7761.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/uart/uart.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace cse7761 {
 | 
			
		||||
 | 
			
		||||
struct CSE7761DataStruct {
 | 
			
		||||
  uint32_t frequency = 0;
 | 
			
		||||
  uint32_t voltage_rms = 0;
 | 
			
		||||
  uint32_t current_rms[2] = {0};
 | 
			
		||||
  uint32_t energy[2] = {0};
 | 
			
		||||
  uint32_t active_power[2] = {0};
 | 
			
		||||
  uint16_t coefficient[8] = {0};
 | 
			
		||||
  uint8_t energy_update = 0;
 | 
			
		||||
  bool ready = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// This class implements support for the CSE7761 UART power sensor.
 | 
			
		||||
class CSE7761Component : public PollingComponent, public uart::UARTDevice {
 | 
			
		||||
 public:
 | 
			
		||||
  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
 | 
			
		||||
  void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
 | 
			
		||||
  void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
 | 
			
		||||
  void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
 | 
			
		||||
  void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  // Sensors
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *power_sensor_1_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_sensor_1_{nullptr};
 | 
			
		||||
  sensor::Sensor *power_sensor_2_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_sensor_2_{nullptr};
 | 
			
		||||
  CSE7761DataStruct data_;
 | 
			
		||||
 | 
			
		||||
  void write_(uint8_t reg, uint16_t data);
 | 
			
		||||
  bool read_once_(uint8_t reg, uint8_t size, uint32_t *value);
 | 
			
		||||
  uint32_t read_(uint8_t reg, uint8_t size);
 | 
			
		||||
  uint32_t coefficient_by_unit_(uint32_t unit);
 | 
			
		||||
  bool chip_init_();
 | 
			
		||||
  void get_data_();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace cse7761
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										90
									
								
								esphome/components/cse7761/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								esphome/components/cse7761/sensor.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome.components import sensor, uart
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    DEVICE_CLASS_CURRENT,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@berfenger"]
 | 
			
		||||
DEPENDENCIES = ["uart"]
 | 
			
		||||
 | 
			
		||||
cse7761_ns = cg.esphome_ns.namespace("cse7761")
 | 
			
		||||
CSE7761Component = cse7761_ns.class_(
 | 
			
		||||
    "CSE7761Component", cg.PollingComponent, uart.UARTDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_CURRENT_1 = "current_1"
 | 
			
		||||
CONF_CURRENT_2 = "current_2"
 | 
			
		||||
CONF_ACTIVE_POWER_1 = "active_power_1"
 | 
			
		||||
CONF_ACTIVE_POWER_2 = "active_power_2"
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(CSE7761Component),
 | 
			
		||||
            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_1): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_AMPERE,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT_2): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_AMPERE,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
                device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("60s"))
 | 
			
		||||
    .extend(uart.UART_DEVICE_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
 | 
			
		||||
    "cse7761", baud_rate=38400, require_rx=True, require_tx=True
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
    await uart.register_uart_device(var, config)
 | 
			
		||||
 | 
			
		||||
    for key in [
 | 
			
		||||
        CONF_VOLTAGE,
 | 
			
		||||
        CONF_CURRENT_1,
 | 
			
		||||
        CONF_CURRENT_2,
 | 
			
		||||
        CONF_ACTIVE_POWER_1,
 | 
			
		||||
        CONF_ACTIVE_POWER_2,
 | 
			
		||||
    ]:
 | 
			
		||||
        if key not in config:
 | 
			
		||||
            continue
 | 
			
		||||
        conf = config[key]
 | 
			
		||||
        sens = await sensor.new_sensor(conf)
 | 
			
		||||
        cg.add(getattr(var, f"set_{key}_sensor")(sens))
 | 
			
		||||
@@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
AUTO_LOAD = ["sensor"]
 | 
			
		||||
 | 
			
		||||
CONF_ONE_WIRE_ID = "one_wire_id"
 | 
			
		||||
dallas_ns = cg.esphome_ns.namespace("dallas")
 | 
			
		||||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
 | 
			
		||||
ESPOneWire = dallas_ns.class_("ESPOneWire")
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(DallasComponent),
 | 
			
		||||
            cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
 | 
			
		||||
            cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
        }
 | 
			
		||||
    ).extend(cv.polling_component_schema("60s")),
 | 
			
		||||
    # pin_mode call logs in esp-idf, but InterruptLock is active -> crash
 | 
			
		||||
    cv.only_with_arduino,
 | 
			
		||||
)
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(DallasComponent),
 | 
			
		||||
        cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.polling_component_schema("60s"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
    one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], one_wire)
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    pin = await cg.gpio_pin_expression(config[CONF_PIN])
 | 
			
		||||
    cg.add(var.set_pin(pin))
 | 
			
		||||
 
 | 
			
		||||
@@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
 | 
			
		||||
void DallasComponent::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
 | 
			
		||||
 | 
			
		||||
  yield();
 | 
			
		||||
  pin_->setup();
 | 
			
		||||
  one_wire_ = new ESPOneWire(pin_);  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
 | 
			
		||||
  std::vector<uint64_t> raw_sensors;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    raw_sensors = this->one_wire_->search_vec();
 | 
			
		||||
  }
 | 
			
		||||
  raw_sensors = this->one_wire_->search_vec();
 | 
			
		||||
 | 
			
		||||
  for (auto &address : raw_sensors) {
 | 
			
		||||
    std::string s = uint64_to_string(address);
 | 
			
		||||
@@ -70,7 +69,7 @@ void DallasComponent::setup() {
 | 
			
		||||
}
 | 
			
		||||
void DallasComponent::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "DallasComponent:");
 | 
			
		||||
  LOG_PIN("  Pin: ", this->one_wire_->get_pin());
 | 
			
		||||
  LOG_PIN("  Pin: ", this->pin_);
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
 | 
			
		||||
  if (this->found_sensors_.empty()) {
 | 
			
		||||
@@ -102,15 +101,12 @@ void DallasComponent::update() {
 | 
			
		||||
  this->status_clear_warning();
 | 
			
		||||
 | 
			
		||||
  bool result;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (!this->one_wire_->reset()) {
 | 
			
		||||
      result = false;
 | 
			
		||||
    } else {
 | 
			
		||||
      result = true;
 | 
			
		||||
      this->one_wire_->skip();
 | 
			
		||||
      this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
 | 
			
		||||
    }
 | 
			
		||||
  if (!this->one_wire_->reset()) {
 | 
			
		||||
    result = false;
 | 
			
		||||
  } else {
 | 
			
		||||
    result = true;
 | 
			
		||||
    this->one_wire_->skip();
 | 
			
		||||
    this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!result) {
 | 
			
		||||
@@ -121,11 +117,7 @@ void DallasComponent::update() {
 | 
			
		||||
 | 
			
		||||
  for (auto *sensor : this->sensors_) {
 | 
			
		||||
    this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
 | 
			
		||||
      bool res;
 | 
			
		||||
      {
 | 
			
		||||
        InterruptLock lock;
 | 
			
		||||
        res = sensor->read_scratch_pad();
 | 
			
		||||
      }
 | 
			
		||||
      bool res = sensor->read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
      if (!res) {
 | 
			
		||||
        ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
 | 
			
		||||
@@ -146,7 +138,6 @@ void DallasComponent::update() {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
 | 
			
		||||
 | 
			
		||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
 | 
			
		||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
 | 
			
		||||
@@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() {
 | 
			
		||||
  return this->address_name_;
 | 
			
		||||
}
 | 
			
		||||
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
 | 
			
		||||
  ESPOneWire *wire = this->parent_->one_wire_;
 | 
			
		||||
  auto *wire = this->parent_->one_wire_;
 | 
			
		||||
  if (!wire->reset()) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
@@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
bool DallasTemperatureSensor::setup_sensor() {
 | 
			
		||||
  bool r;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    r = this->read_scratch_pad();
 | 
			
		||||
  }
 | 
			
		||||
  bool r = this->read_scratch_pad();
 | 
			
		||||
 | 
			
		||||
  if (!r) {
 | 
			
		||||
    ESP_LOGE(TAG, "Reading scratchpad failed: reset");
 | 
			
		||||
@@ -214,21 +201,18 @@ bool DallasTemperatureSensor::setup_sensor() {
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESPOneWire *wire = this->parent_->one_wire_;
 | 
			
		||||
  {
 | 
			
		||||
    InterruptLock lock;
 | 
			
		||||
    if (wire->reset()) {
 | 
			
		||||
      wire->select(this->address_);
 | 
			
		||||
      wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
 | 
			
		||||
      wire->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
      wire->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
      wire->write8(this->scratch_pad_[4]);  // resolution
 | 
			
		||||
      wire->reset();
 | 
			
		||||
  auto *wire = this->parent_->one_wire_;
 | 
			
		||||
  if (wire->reset()) {
 | 
			
		||||
    wire->select(this->address_);
 | 
			
		||||
    wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
 | 
			
		||||
    wire->write8(this->scratch_pad_[2]);  // high alarm temp
 | 
			
		||||
    wire->write8(this->scratch_pad_[3]);  // low alarm temp
 | 
			
		||||
    wire->write8(this->scratch_pad_[4]);  // resolution
 | 
			
		||||
    wire->reset();
 | 
			
		||||
 | 
			
		||||
      // write value to EEPROM
 | 
			
		||||
      wire->select(this->address_);
 | 
			
		||||
      wire->write8(0x48);
 | 
			
		||||
    }
 | 
			
		||||
    // write value to EEPROM
 | 
			
		||||
    wire->select(this->address_);
 | 
			
		||||
    wire->write8(0x48);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delay(20);  // allow it to finish operation
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,7 @@ class DallasTemperatureSensor;
 | 
			
		||||
 | 
			
		||||
class DallasComponent : public PollingComponent {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit DallasComponent(ESPOneWire *one_wire);
 | 
			
		||||
 | 
			
		||||
  void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
 | 
			
		||||
  void register_sensor(DallasTemperatureSensor *sensor);
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
@@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent {
 | 
			
		||||
 protected:
 | 
			
		||||
  friend DallasTemperatureSensor;
 | 
			
		||||
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
  ESPOneWire *one_wire_;
 | 
			
		||||
  std::vector<DallasTemperatureSensor *> sensors_;
 | 
			
		||||
  std::vector<uint64_t> found_sensors_;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire";
 | 
			
		||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
 | 
			
		||||
const int ONE_WIRE_ROM_SEARCH = 0xF0;
 | 
			
		||||
 | 
			
		||||
ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {}
 | 
			
		||||
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR ESPOneWire::reset() {
 | 
			
		||||
  uint8_t retries = 125;
 | 
			
		||||
  // See reset here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
 | 
			
		||||
  // Wait for communication to clear
 | 
			
		||||
  this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  // Wait for communication to clear (delay G)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  uint8_t retries = 125;
 | 
			
		||||
  do {
 | 
			
		||||
    if (--retries == 0)
 | 
			
		||||
      return false;
 | 
			
		||||
    delayMicroseconds(2);
 | 
			
		||||
  } while (!this->pin_->digital_read());
 | 
			
		||||
  } while (!pin_.digital_read());
 | 
			
		||||
 | 
			
		||||
  // Send 480µs LOW TX reset pulse
 | 
			
		||||
  this->pin_->pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  this->pin_->digital_write(false);
 | 
			
		||||
  // Send 480µs LOW TX reset pulse (drive bus low, delay H)
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  delayMicroseconds(480);
 | 
			
		||||
 | 
			
		||||
  // Switch into RX mode, letting the pin float
 | 
			
		||||
  this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  // after 15µs-60µs wait time, responder pulls low for 60µs-240µs
 | 
			
		||||
  // let's have 70µs just in case
 | 
			
		||||
  // Release the bus, delay I
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(70);
 | 
			
		||||
 | 
			
		||||
  bool r = !this->pin_->digital_read();
 | 
			
		||||
  // sample bus, 0=device(s) present, 1=no device present
 | 
			
		||||
  bool r = !pin_.digital_read();
 | 
			
		||||
  // delay J
 | 
			
		||||
  delayMicroseconds(410);
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
 | 
			
		||||
  // Initiate write/read by pulling low.
 | 
			
		||||
  this->pin_->pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  this->pin_->digital_write(false);
 | 
			
		||||
  // See write 1/0 bit here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
 | 
			
		||||
  // bus sampled within 15µs and 60µs after pulling LOW.
 | 
			
		||||
  if (bit) {
 | 
			
		||||
    // pull high/release within 15µs
 | 
			
		||||
    delayMicroseconds(10);
 | 
			
		||||
    this->pin_->digital_write(true);
 | 
			
		||||
    // in total minimum of 60µs long
 | 
			
		||||
    delayMicroseconds(55);
 | 
			
		||||
  } else {
 | 
			
		||||
    // continue pulling LOW for at least 60µs
 | 
			
		||||
    delayMicroseconds(65);
 | 
			
		||||
    this->pin_->digital_write(true);
 | 
			
		||||
    // grace period, 1µs recovery time
 | 
			
		||||
    delayMicroseconds(5);
 | 
			
		||||
  }
 | 
			
		||||
  // drive bus low
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
 | 
			
		||||
  uint32_t delay0 = bit ? 10 : 65;
 | 
			
		||||
  uint32_t delay1 = bit ? 55 : 5;
 | 
			
		||||
 | 
			
		||||
  // delay A/C
 | 
			
		||||
  delayMicroseconds(delay0);
 | 
			
		||||
  // release bus
 | 
			
		||||
  pin_.digital_write(true);
 | 
			
		||||
  // delay B/D
 | 
			
		||||
  delayMicroseconds(delay1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
 | 
			
		||||
  // Initiate read slot by pulling LOW for at least 1µs
 | 
			
		||||
  this->pin_->pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  this->pin_->digital_write(false);
 | 
			
		||||
  // See read bit here:
 | 
			
		||||
  // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
 | 
			
		||||
  InterruptLock lock;
 | 
			
		||||
 | 
			
		||||
  // drive bus low, delay A
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_OUTPUT);
 | 
			
		||||
  pin_.digital_write(false);
 | 
			
		||||
  delayMicroseconds(3);
 | 
			
		||||
 | 
			
		||||
  // release bus, we have to sample within 15µs of pulling low
 | 
			
		||||
  this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  // release bus, delay E
 | 
			
		||||
  pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
 | 
			
		||||
  delayMicroseconds(10);
 | 
			
		||||
 | 
			
		||||
  bool r = this->pin_->digital_read();
 | 
			
		||||
  // read time slot at least 60µs long + 1µs recovery time between slots
 | 
			
		||||
  // sample bus to read bit from peer
 | 
			
		||||
  bool r = pin_.digital_read();
 | 
			
		||||
 | 
			
		||||
  // delay F
 | 
			
		||||
  delayMicroseconds(53);
 | 
			
		||||
  return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
 | 
			
		||||
void ESPOneWire::write8(uint8_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    this->write_bit(bool((1u << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
 | 
			
		||||
void ESPOneWire::write64(uint64_t val) {
 | 
			
		||||
  for (uint8_t i = 0; i < 64; i++) {
 | 
			
		||||
    this->write_bit(bool((1ULL << i) & val));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR ESPOneWire::read8() {
 | 
			
		||||
uint8_t ESPOneWire::read8() {
 | 
			
		||||
  uint8_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint8_t(this->read_bit()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
uint64_t IRAM_ATTR ESPOneWire::read64() {
 | 
			
		||||
uint64_t ESPOneWire::read64() {
 | 
			
		||||
  uint64_t ret = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
    ret |= (uint64_t(this->read_bit()) << i);
 | 
			
		||||
  }
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
 | 
			
		||||
void ESPOneWire::select(uint64_t address) {
 | 
			
		||||
  this->write8(ONE_WIRE_ROM_SELECT);
 | 
			
		||||
  this->write64(address);
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::reset_search() {
 | 
			
		||||
void ESPOneWire::reset_search() {
 | 
			
		||||
  this->last_discrepancy_ = 0;
 | 
			
		||||
  this->last_device_flag_ = false;
 | 
			
		||||
  this->last_family_discrepancy_ = 0;
 | 
			
		||||
  this->rom_number_ = 0;
 | 
			
		||||
}
 | 
			
		||||
uint64_t HOT IRAM_ATTR ESPOneWire::search() {
 | 
			
		||||
uint64_t ESPOneWire::search() {
 | 
			
		||||
  if (this->last_device_flag_) {
 | 
			
		||||
    return 0u;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!this->reset()) {
 | 
			
		||||
    // Reset failed
 | 
			
		||||
    // Reset failed or no devices present
 | 
			
		||||
    this->reset_search();
 | 
			
		||||
    return 0u;
 | 
			
		||||
  }
 | 
			
		||||
@@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() {
 | 
			
		||||
 | 
			
		||||
  return this->rom_number_;
 | 
			
		||||
}
 | 
			
		||||
std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
 | 
			
		||||
std::vector<uint64_t> ESPOneWire::search_vec() {
 | 
			
		||||
  std::vector<uint64_t> res;
 | 
			
		||||
 | 
			
		||||
  this->reset_search();
 | 
			
		||||
@@ -206,10 +214,9 @@ std::vector<uint64_t> IRAM_ATTR ESPOneWire::search_vec() {
 | 
			
		||||
 | 
			
		||||
  return res;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ESPOneWire::skip() {
 | 
			
		||||
void ESPOneWire::skip() {
 | 
			
		||||
  this->write8(0xCC);  // skip ROM
 | 
			
		||||
}
 | 
			
		||||
GPIOPin *ESPOneWire::get_pin() { return this->pin_; }
 | 
			
		||||
 | 
			
		||||
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
@@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH;
 | 
			
		||||
 | 
			
		||||
class ESPOneWire {
 | 
			
		||||
 public:
 | 
			
		||||
  explicit ESPOneWire(GPIOPin *pin);
 | 
			
		||||
  explicit ESPOneWire(InternalGPIOPin *pin);
 | 
			
		||||
 | 
			
		||||
  /** Reset the bus, should be done before all write operations.
 | 
			
		||||
   *
 | 
			
		||||
@@ -55,13 +54,11 @@ class ESPOneWire {
 | 
			
		||||
  /// Helper that wraps search in a std::vector.
 | 
			
		||||
  std::vector<uint64_t> search_vec();
 | 
			
		||||
 | 
			
		||||
  GPIOPin *get_pin();
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  /// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
 | 
			
		||||
  inline uint8_t *rom_number8_();
 | 
			
		||||
 | 
			
		||||
  GPIOPin *pin_;
 | 
			
		||||
  ISRInternalGPIOPin pin_;
 | 
			
		||||
  uint8_t last_discrepancy_{0};
 | 
			
		||||
  uint8_t last_family_discrepancy_{0};
 | 
			
		||||
  bool last_device_flag_{false};
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,14 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
WIFI_MESSAGE = """
 | 
			
		||||
 | 
			
		||||
# Do not forget to add your own wifi configuration before installing this configuration
 | 
			
		||||
# wifi:
 | 
			
		||||
#   ssid: !secret wifi_ssid
 | 
			
		||||
#   password: !secret wifi_password
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add_define("USE_DASHBOARD_IMPORT")
 | 
			
		||||
@@ -41,5 +49,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
 | 
			
		||||
    if p.exists():
 | 
			
		||||
        raise FileExistsError
 | 
			
		||||
 | 
			
		||||
    config = {"substitutions": {"name": name}, "packages": {project_name: import_url}}
 | 
			
		||||
    p.write_text(dump(config), encoding="utf8")
 | 
			
		||||
    config = {
 | 
			
		||||
        "substitutions": {"name": name},
 | 
			
		||||
        "packages": {project_name: import_url},
 | 
			
		||||
        "esphome": {"name_add_mac_suffix": False},
 | 
			
		||||
    }
 | 
			
		||||
    p.write_text(
 | 
			
		||||
        dump(config) + WIFI_MESSAGE,
 | 
			
		||||
        encoding="utf8",
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -78,8 +78,9 @@ void DeepSleepComponent::begin_sleep(bool manual) {
 | 
			
		||||
    esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
 | 
			
		||||
  if (this->wakeup_pin_ != nullptr) {
 | 
			
		||||
    bool level = this->wakeup_pin_->is_inverted();
 | 
			
		||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read())
 | 
			
		||||
    if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) {
 | 
			
		||||
      level = !level;
 | 
			
		||||
    }
 | 
			
		||||
    esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->ext1_wakeup_.has_value()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
 | 
			
		||||
from esphome import core, automation
 | 
			
		||||
from esphome.automation import maybe_simple_id
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_AUTO_CLEAR_ENABLED,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_LAMBDA,
 | 
			
		||||
    CONF_PAGES,
 | 
			
		||||
@@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
 | 
			
		||||
                cv.Optional(CONF_TO): cv.use_id(DisplayPage),
 | 
			
		||||
            }
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
 | 
			
		||||
async def setup_display_core_(var, config):
 | 
			
		||||
    if CONF_ROTATION in config:
 | 
			
		||||
        cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
 | 
			
		||||
 | 
			
		||||
    if CONF_AUTO_CLEAR_ENABLED in config:
 | 
			
		||||
        cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED]))
 | 
			
		||||
 | 
			
		||||
    if CONF_PAGES in config:
 | 
			
		||||
        pages = []
 | 
			
		||||
        for conf in config[CONF_PAGES]:
 | 
			
		||||
 
 | 
			
		||||
@@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) {
 | 
			
		||||
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
 | 
			
		||||
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
 | 
			
		||||
void DisplayBuffer::do_update_() {
 | 
			
		||||
  this->clear();
 | 
			
		||||
  if (this->auto_clear_enabled_) {
 | 
			
		||||
    this->clear();
 | 
			
		||||
  }
 | 
			
		||||
  if (this->page_ != nullptr) {
 | 
			
		||||
    this->page_->get_writer()(*this);
 | 
			
		||||
  } else if (this->writer_.has_value()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -333,6 +333,9 @@ class DisplayBuffer {
 | 
			
		||||
  /// Internal method to set the display rotation with.
 | 
			
		||||
  void set_rotation(DisplayRotation rotation);
 | 
			
		||||
 | 
			
		||||
  // Internal method to set display auto clearing.
 | 
			
		||||
  void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
 | 
			
		||||
 | 
			
		||||
@@ -352,6 +355,7 @@ class DisplayBuffer {
 | 
			
		||||
  DisplayPage *page_{nullptr};
 | 
			
		||||
  DisplayPage *previous_page_{nullptr};
 | 
			
		||||
  std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
 | 
			
		||||
  bool auto_clear_enabled_{true};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DisplayPage {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,14 +19,30 @@ void Dsmr::loop() {
 | 
			
		||||
    this->receive_encrypted_();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Dsmr::available_within_timeout_() {
 | 
			
		||||
  uint8_t tries = READ_TIMEOUT_MS / 5;
 | 
			
		||||
  while (tries--) {
 | 
			
		||||
    delay(5);
 | 
			
		||||
    if (available()) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::receive_telegram_() {
 | 
			
		||||
  int count = MAX_BYTES_PER_LOOP;
 | 
			
		||||
  while (available() && count-- > 0) {
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (!available()) {
 | 
			
		||||
      if (!header_found_ || !available_within_timeout_()) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const char c = read();
 | 
			
		||||
 | 
			
		||||
    // Find a new telegram header, i.e. forward slash.
 | 
			
		||||
    if (c == '/') {
 | 
			
		||||
      ESP_LOGV(TAG, "Header found");
 | 
			
		||||
      ESP_LOGV(TAG, "Header of telegram found");
 | 
			
		||||
      header_found_ = true;
 | 
			
		||||
      footer_found_ = false;
 | 
			
		||||
      telegram_len_ = 0;
 | 
			
		||||
@@ -38,7 +54,7 @@ void Dsmr::receive_telegram_() {
 | 
			
		||||
    if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      footer_found_ = false;
 | 
			
		||||
      ESP_LOGE(TAG, "Error: Message larger than buffer");
 | 
			
		||||
      ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -54,16 +70,17 @@ void Dsmr::receive_telegram_() {
 | 
			
		||||
 | 
			
		||||
    // Check for a footer, i.e. exlamation mark, followed by a hex checksum.
 | 
			
		||||
    if (c == '!') {
 | 
			
		||||
      ESP_LOGV(TAG, "Footer found");
 | 
			
		||||
      ESP_LOGV(TAG, "Footer of telegram found");
 | 
			
		||||
      footer_found_ = true;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    // Check for the end of the hex checksum, i.e. a newline.
 | 
			
		||||
    if (footer_found_ && c == '\n') {
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      // Parse the telegram and publish sensor values.
 | 
			
		||||
      if (parse_telegram())
 | 
			
		||||
        return;
 | 
			
		||||
      parse_telegram();
 | 
			
		||||
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -72,41 +89,46 @@ void Dsmr::receive_encrypted_() {
 | 
			
		||||
  // Encrypted buffer
 | 
			
		||||
  uint8_t buffer[MAX_TELEGRAM_LENGTH];
 | 
			
		||||
  size_t buffer_length = 0;
 | 
			
		||||
 | 
			
		||||
  size_t packet_size = 0;
 | 
			
		||||
  while (available()) {
 | 
			
		||||
    const char c = read();
 | 
			
		||||
 | 
			
		||||
    if (!header_found_) {
 | 
			
		||||
      if ((uint8_t) c == 0xdb) {
 | 
			
		||||
        ESP_LOGV(TAG, "Start byte 0xDB found");
 | 
			
		||||
        header_found_ = true;
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (!available()) {
 | 
			
		||||
      if (!header_found_) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (!available_within_timeout_()) {
 | 
			
		||||
        ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Sanity check
 | 
			
		||||
    if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) {
 | 
			
		||||
      if (buffer_length == 0) {
 | 
			
		||||
        ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting.");
 | 
			
		||||
      } else {
 | 
			
		||||
        ESP_LOGW(TAG, "Unexpected data");
 | 
			
		||||
    const char c = read();
 | 
			
		||||
 | 
			
		||||
    // Find a new telegram start byte.
 | 
			
		||||
    if (!header_found_) {
 | 
			
		||||
      if ((uint8_t) c != 0xDB) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      this->status_momentary_warning("unexpected_data");
 | 
			
		||||
      this->flush();
 | 
			
		||||
      while (available())
 | 
			
		||||
        read();
 | 
			
		||||
      ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
 | 
			
		||||
      header_found_ = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check for buffer overflow.
 | 
			
		||||
    if (buffer_length >= MAX_TELEGRAM_LENGTH) {
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buffer[buffer_length++] = c;
 | 
			
		||||
 | 
			
		||||
    if (packet_size == 0 && buffer_length > 20) {
 | 
			
		||||
      // Complete header + a few bytes of data
 | 
			
		||||
      packet_size = buffer[11] << 8 | buffer[12];
 | 
			
		||||
      // Complete header + data bytes
 | 
			
		||||
      packet_size = 13 + (buffer[11] << 8 | buffer[12]);
 | 
			
		||||
      ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
 | 
			
		||||
    }
 | 
			
		||||
    if (buffer_length == packet_size + 13 && packet_size > 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length);
 | 
			
		||||
 | 
			
		||||
    if (buffer_length == packet_size && packet_size > 0) {
 | 
			
		||||
      ESP_LOGV(TAG, "End of encrypted telegram found");
 | 
			
		||||
      GCM<AES128> *gcmaes128{new GCM<AES128>()};
 | 
			
		||||
      gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
 | 
			
		||||
      // the iv is 8 bytes of the system title + 4 bytes frame counter
 | 
			
		||||
@@ -123,28 +145,21 @@ void Dsmr::receive_encrypted_() {
 | 
			
		||||
      delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
 | 
			
		||||
      telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
 | 
			
		||||
      ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_);
 | 
			
		||||
      ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_);
 | 
			
		||||
      ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
 | 
			
		||||
      ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
 | 
			
		||||
 | 
			
		||||
      parse_telegram();
 | 
			
		||||
 | 
			
		||||
      header_found_ = false;
 | 
			
		||||
      telegram_len_ = 0;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!available()) {
 | 
			
		||||
      // baud rate is 115200 for encrypted data, this means a few byte should arrive every time
 | 
			
		||||
      // program runs faster than buffer loading then available() might return false in the middle
 | 
			
		||||
      delay(4);  // Wait for data
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (buffer_length > 0) {
 | 
			
		||||
    ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Dsmr::parse_telegram() {
 | 
			
		||||
  MyData data;
 | 
			
		||||
  ESP_LOGV(TAG, "Trying to parse");
 | 
			
		||||
  ESP_LOGV(TAG, "Trying to parse telegram");
 | 
			
		||||
  ::dsmr::ParseResult<void> res =
 | 
			
		||||
      ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
 | 
			
		||||
                              this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values.
 | 
			
		||||
@@ -161,7 +176,7 @@ bool Dsmr::parse_telegram() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Dsmr::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "dsmr:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "DSMR:");
 | 
			
		||||
 | 
			
		||||
#define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_);
 | 
			
		||||
  DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
 | 
			
		||||
@@ -178,12 +193,12 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (decryption_key.length() != 32) {
 | 
			
		||||
    ESP_LOGE(TAG, "Error, decryption key must be 32 character long.");
 | 
			
		||||
    ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  this->decryption_key_.clear();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "Decryption key is set.");
 | 
			
		||||
  ESP_LOGI(TAG, "Decryption key is set");
 | 
			
		||||
  // Verbose level prints decryption key
 | 
			
		||||
  ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,7 @@ namespace esphome {
 | 
			
		||||
namespace dsmr {
 | 
			
		||||
 | 
			
		||||
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
 | 
			
		||||
static constexpr uint32_t MAX_BYTES_PER_LOOP = 50;
 | 
			
		||||
static constexpr uint32_t POLL_TIMEOUT = 1000;
 | 
			
		||||
static constexpr uint32_t READ_TIMEOUT_MS = 200;
 | 
			
		||||
 | 
			
		||||
using namespace ::dsmr::fields;
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +85,17 @@ class Dsmr : public Component, public uart::UARTDevice {
 | 
			
		||||
  void receive_telegram_();
 | 
			
		||||
  void receive_encrypted_();
 | 
			
		||||
 | 
			
		||||
  /// Wait for UART data to become available within the read timeout.
 | 
			
		||||
  ///
 | 
			
		||||
  /// The smart meter might provide data in chunks, causing available() to
 | 
			
		||||
  /// return 0. When we're already reading a telegram, then we don't return
 | 
			
		||||
  /// right away (to handle further data in an upcoming loop) but wait a
 | 
			
		||||
  /// little while using this method to see if more data are incoming.
 | 
			
		||||
  /// By not returning, we prevent other components from taking so much
 | 
			
		||||
  /// time that the UART RX buffer overflows and bytes of the telegram get
 | 
			
		||||
  /// lost in the process.
 | 
			
		||||
  bool available_within_timeout_();
 | 
			
		||||
 | 
			
		||||
  // Telegram buffer
 | 
			
		||||
  char telegram_[MAX_TELEGRAM_LENGTH];
 | 
			
		||||
  int telegram_len_{0};
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ void DutyCycleSensor::setup() {
 | 
			
		||||
  this->pin_->setup();
 | 
			
		||||
  this->store_.pin = this->pin_->to_isr();
 | 
			
		||||
  this->store_.last_level = this->pin_->digital_read();
 | 
			
		||||
  this->last_update_ = micros();
 | 
			
		||||
  this->store_.last_interrupt = micros();
 | 
			
		||||
 | 
			
		||||
  this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
 | 
			
		||||
@@ -24,19 +23,20 @@ void DutyCycleSensor::dump_config() {
 | 
			
		||||
}
 | 
			
		||||
void DutyCycleSensor::update() {
 | 
			
		||||
  const uint32_t now = micros();
 | 
			
		||||
  const bool level = this->store_.last_level;
 | 
			
		||||
  const uint32_t last_interrupt = this->store_.last_interrupt;
 | 
			
		||||
  uint32_t on_time = this->store_.on_time;
 | 
			
		||||
  if (this->last_update_ != 0) {
 | 
			
		||||
    const bool level = this->store_.last_level;
 | 
			
		||||
    const uint32_t last_interrupt = this->store_.last_interrupt;
 | 
			
		||||
    uint32_t on_time = this->store_.on_time;
 | 
			
		||||
 | 
			
		||||
  if (level)
 | 
			
		||||
    on_time += now - last_interrupt;
 | 
			
		||||
    if (level)
 | 
			
		||||
      on_time += now - last_interrupt;
 | 
			
		||||
 | 
			
		||||
  const float total_time = float(now - this->last_update_);
 | 
			
		||||
 | 
			
		||||
  const float value = (on_time / total_time) * 100.0f;
 | 
			
		||||
  ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
 | 
			
		||||
  this->publish_state(value);
 | 
			
		||||
    const float total_time = float(now - this->last_update_);
 | 
			
		||||
 | 
			
		||||
    const float value = (on_time / total_time) * 100.0f;
 | 
			
		||||
    ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
 | 
			
		||||
    this->publish_state(value);
 | 
			
		||||
  }
 | 
			
		||||
  this->store_.on_time = 0;
 | 
			
		||||
  this->store_.last_interrupt = now;
 | 
			
		||||
  this->last_update_ = now;
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
 | 
			
		||||
  InternalGPIOPin *pin_;
 | 
			
		||||
 | 
			
		||||
  DutyCycleSensorStore store_{};
 | 
			
		||||
  uint32_t last_update_;
 | 
			
		||||
  uint32_t last_update_{0};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace duty_cycle
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ from .const import (  # noqa
 | 
			
		||||
    KEY_SDKCONFIG_OPTIONS,
 | 
			
		||||
    KEY_VARIANT,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_FRIENDLY,
 | 
			
		||||
    VARIANTS,
 | 
			
		||||
)
 | 
			
		||||
from .boards import BOARD_TO_VARIANT
 | 
			
		||||
@@ -147,8 +148,9 @@ def _arduino_check_versions(value):
 | 
			
		||||
    value[CONF_VERSION] = str(version)
 | 
			
		||||
    value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
 | 
			
		||||
 | 
			
		||||
    platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION)
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = str(platform_version)
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = value.get(
 | 
			
		||||
        CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
@@ -184,8 +186,9 @@ def _esp_idf_check_versions(value):
 | 
			
		||||
    value[CONF_VERSION] = str(version)
 | 
			
		||||
    value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
 | 
			
		||||
 | 
			
		||||
    platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION)
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = str(platform_version)
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = value.get(
 | 
			
		||||
        CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
@@ -196,6 +199,15 @@ def _esp_idf_check_versions(value):
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _parse_platform_version(value):
 | 
			
		||||
    try:
 | 
			
		||||
        # if platform version is a valid version constraint, prefix the default package
 | 
			
		||||
        cv.platformio_version_constraint(value)
 | 
			
		||||
        return f"platformio/espressif32 @ {value}"
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _detect_variant(value):
 | 
			
		||||
    if CONF_VARIANT not in value:
 | 
			
		||||
        board = value[CONF_BOARD]
 | 
			
		||||
@@ -218,7 +230,7 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_SOURCE): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    _arduino_check_versions,
 | 
			
		||||
@@ -230,7 +242,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_SOURCE): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
 | 
			
		||||
            cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
 | 
			
		||||
                cv.string_strict: cv.string_strict
 | 
			
		||||
            },
 | 
			
		||||
@@ -276,14 +288,14 @@ async def to_code(config):
 | 
			
		||||
    cg.add_build_flag("-DUSE_ESP32")
 | 
			
		||||
    cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
 | 
			
		||||
    cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
 | 
			
		||||
    cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
 | 
			
		||||
 | 
			
		||||
    cg.add_platformio_option("lib_ldf_mode", "off")
 | 
			
		||||
 | 
			
		||||
    conf = config[CONF_FRAMEWORK]
 | 
			
		||||
    cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
 | 
			
		||||
 | 
			
		||||
    if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
 | 
			
		||||
        cg.add_platformio_option(
 | 
			
		||||
            "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
 | 
			
		||||
        )
 | 
			
		||||
        cg.add_platformio_option("framework", "espidf")
 | 
			
		||||
        cg.add_build_flag("-DUSE_ESP_IDF")
 | 
			
		||||
        cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
 | 
			
		||||
@@ -299,6 +311,8 @@ async def to_code(config):
 | 
			
		||||
        )
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
 | 
			
		||||
        # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
 | 
			
		||||
        add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
 | 
			
		||||
 | 
			
		||||
        cg.add_platformio_option("board_build.partitions", "partitions.csv")
 | 
			
		||||
 | 
			
		||||
@@ -312,9 +326,6 @@ async def to_code(config):
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
 | 
			
		||||
        cg.add_platformio_option(
 | 
			
		||||
            "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
 | 
			
		||||
        )
 | 
			
		||||
        cg.add_platformio_option("framework", "arduino")
 | 
			
		||||
        cg.add_build_flag("-DUSE_ARDUINO")
 | 
			
		||||
        cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
 | 
			
		||||
 
 | 
			
		||||
@@ -18,4 +18,12 @@ VARIANTS = [
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
VARIANT_FRIENDLY = {
 | 
			
		||||
    VARIANT_ESP32: "ESP32",
 | 
			
		||||
    VARIANT_ESP32S2: "ESP32-S2",
 | 
			
		||||
    VARIANT_ESP32S3: "ESP32-S3",
 | 
			
		||||
    VARIANT_ESP32C3: "ESP32-C3",
 | 
			
		||||
    VARIANT_ESP32H2: "ESP32-H2",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp32_ns = cg.esphome_ns.namespace("esp32")
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,7 @@ void IRAM_ATTR HOT yield() { vPortYield(); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
 | 
			
		||||
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
 | 
			
		||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
 | 
			
		||||
  auto start = (uint64_t) esp_timer_get_time();
 | 
			
		||||
  while (((uint64_t) esp_timer_get_time()) - start < us)
 | 
			
		||||
    ;
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
 | 
			
		||||
void arch_restart() {
 | 
			
		||||
  esp_restart();
 | 
			
		||||
  // restart() doesn't always end execution
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import logging
 | 
			
		||||
from dataclasses import dataclass
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
@@ -17,10 +18,24 @@ import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
 | 
			
		||||
from . import boards
 | 
			
		||||
from .const import KEY_BOARD, KEY_ESP32, esp32_ns
 | 
			
		||||
from .const import (
 | 
			
		||||
    KEY_BOARD,
 | 
			
		||||
    KEY_ESP32,
 | 
			
		||||
    KEY_VARIANT,
 | 
			
		||||
    VARIANT_ESP32,
 | 
			
		||||
    VARIANT_ESP32C3,
 | 
			
		||||
    VARIANT_ESP32S2,
 | 
			
		||||
    VARIANT_ESP32S3,
 | 
			
		||||
    VARIANT_ESP32H2,
 | 
			
		||||
    esp32_ns,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
 | 
			
		||||
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
 | 
			
		||||
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
 | 
			
		||||
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
 | 
			
		||||
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
 | 
			
		||||
@@ -59,65 +74,61 @@ def _translate_pin(value):
 | 
			
		||||
    return _lookup_pin(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_ESP_SDIO_PINS = {
 | 
			
		||||
    6: "Flash Clock",
 | 
			
		||||
    7: "Flash Data 0",
 | 
			
		||||
    8: "Flash Data 1",
 | 
			
		||||
    11: "Flash Command",
 | 
			
		||||
@dataclass
 | 
			
		||||
class ESP32ValidationFunctions:
 | 
			
		||||
    pin_validation: Any
 | 
			
		||||
    usage_validation: Any
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_esp32_validations = {
 | 
			
		||||
    VARIANT_ESP32: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32S2: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_s2_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_s2_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32C3: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_c3_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_c3_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32S3: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_s3_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_s3_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
    VARIANT_ESP32H2: ESP32ValidationFunctions(
 | 
			
		||||
        pin_validation=esp32_h2_validate_gpio_pin,
 | 
			
		||||
        usage_validation=esp32_h2_validate_supports,
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_gpio_pin(value):
 | 
			
		||||
    value = _translate_pin(value)
 | 
			
		||||
    if value < 0 or value > 39:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
 | 
			
		||||
    if value in _ESP_SDIO_PINS:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
 | 
			
		||||
        )
 | 
			
		||||
    if 9 <= value <= 10:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "Pin %s (9-10) might already be used by the "
 | 
			
		||||
            "flash interface in QUAD IO flash mode.",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
    if value in (20, 24, 28, 29, 30, 31):
 | 
			
		||||
        # These pins are not exposed in GPIO mux (reason unknown)
 | 
			
		||||
        # but they're missing from IO_MUX list in datasheet
 | 
			
		||||
        raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
 | 
			
		||||
    return value
 | 
			
		||||
    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
			
		||||
    if variant not in _esp32_validations:
 | 
			
		||||
        raise cv.Invalid("Unsupported ESP32 variant {variant}")
 | 
			
		||||
 | 
			
		||||
    return _esp32_validations[variant].pin_validation(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_supports(value):
 | 
			
		||||
    num = value[CONF_NUMBER]
 | 
			
		||||
    mode = value[CONF_MODE]
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
    is_output = mode[CONF_OUTPUT]
 | 
			
		||||
    is_open_drain = mode[CONF_OPEN_DRAIN]
 | 
			
		||||
    is_pullup = mode[CONF_PULLUP]
 | 
			
		||||
    is_pulldown = mode[CONF_PULLDOWN]
 | 
			
		||||
    variant = CORE.data[KEY_ESP32][KEY_VARIANT]
 | 
			
		||||
    if variant not in _esp32_validations:
 | 
			
		||||
        raise cv.Invalid("Unsupported ESP32 variant {variant}")
 | 
			
		||||
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
        pass
 | 
			
		||||
    if is_output and 34 <= num <= 39:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} (34-39) does not support output pin mode.",
 | 
			
		||||
            [CONF_MODE, CONF_OUTPUT],
 | 
			
		||||
        )
 | 
			
		||||
    if is_open_drain and not is_output:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
 | 
			
		||||
        )
 | 
			
		||||
    if is_pullup and 34 <= num <= 39:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
 | 
			
		||||
        )
 | 
			
		||||
    if is_pulldown and 34 <= num <= 39:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    value = _esp32_validations[variant].usage_validation(value)
 | 
			
		||||
    if CORE.using_arduino:
 | 
			
		||||
        # (input, output, open_drain, pullup, pulldown)
 | 
			
		||||
        supported_modes = {
 | 
			
		||||
@@ -138,7 +149,6 @@ def validate_supports(value):
 | 
			
		||||
                "This pin mode is not supported on ESP32 for arduino frameworks",
 | 
			
		||||
                [CONF_MODE],
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,22 @@ namespace esp32 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32";
 | 
			
		||||
 | 
			
		||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    return INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    return OUTPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    return INPUT_PULLUP;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    return INPUT_PULLDOWN;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
    return OUTPUT_OPEN_DRAIN;
 | 
			
		||||
  } else {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  uint8_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
@@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g
 | 
			
		||||
 | 
			
		||||
  attachInterruptArg(pin_, func, arg, arduino_mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  uint8_t mode;
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    mode = INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    mode = OUTPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    mode = INPUT_PULLUP;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    mode = INPUT_PULLDOWN;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
    mode = OUTPUT_OPEN_DRAIN;
 | 
			
		||||
  } else {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  pinMode(pin_, mode);  // NOLINT
 | 
			
		||||
  pinMode(pin_, flags_to_mode(flags));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ArduinoInternalGPIOPin::dump_summary() const {
 | 
			
		||||
@@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  }
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  pinMode(arg->pin, flags_to_mode(flags));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								esphome/components/esp32/gpio_esp32.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/esp32/gpio_esp32.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_OUTPUT,
 | 
			
		||||
    CONF_PULLDOWN,
 | 
			
		||||
    CONF_PULLUP,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_ESP_SDIO_PINS = {
 | 
			
		||||
    6: "Flash Clock",
 | 
			
		||||
    7: "Flash Data 0",
 | 
			
		||||
    8: "Flash Data 1",
 | 
			
		||||
    11: "Flash Command",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
_ESP32_STRAPPING_PINS = {0, 2, 4, 15}
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_validate_gpio_pin(value):
 | 
			
		||||
    if value < 0 or value > 39:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
 | 
			
		||||
    if value in _ESP_SDIO_PINS:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
 | 
			
		||||
        )
 | 
			
		||||
    if 9 <= value <= 10:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "Pin %s (9-10) might already be used by the "
 | 
			
		||||
            "flash interface in QUAD IO flash mode.",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
    if value in _ESP32_STRAPPING_PINS:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "GPIO%d is a Strapping PIN and should be avoided.\n"
 | 
			
		||||
            "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
 | 
			
		||||
            "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
    if value in (20, 24, 28, 29, 30, 31):
 | 
			
		||||
        # These pins are not exposed in GPIO mux (reason unknown)
 | 
			
		||||
        # but they're missing from IO_MUX list in datasheet
 | 
			
		||||
        raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_validate_supports(value):
 | 
			
		||||
    num = value[CONF_NUMBER]
 | 
			
		||||
    mode = value[CONF_MODE]
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
    is_output = mode[CONF_OUTPUT]
 | 
			
		||||
    is_pullup = mode[CONF_PULLUP]
 | 
			
		||||
    is_pulldown = mode[CONF_PULLDOWN]
 | 
			
		||||
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
        pass
 | 
			
		||||
    if is_output and 34 <= num <= 39:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} (34-39) does not support output pin mode.",
 | 
			
		||||
            [CONF_MODE, CONF_OUTPUT],
 | 
			
		||||
        )
 | 
			
		||||
    if is_pullup and 34 <= num <= 39:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
 | 
			
		||||
        )
 | 
			
		||||
    if is_pulldown and 34 <= num <= 39:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
							
								
								
									
										53
									
								
								esphome/components/esp32/gpio_esp32_c3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								esphome/components/esp32/gpio_esp32_c3.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
)
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
_ESP32C3_SPI_PSRAM_PINS = {
 | 
			
		||||
    12: "SPIHD",
 | 
			
		||||
    13: "SPIWP",
 | 
			
		||||
    14: "SPICS0",
 | 
			
		||||
    15: "SPICLK",
 | 
			
		||||
    16: "SPID",
 | 
			
		||||
    17: "SPIQ",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
_ESP32C3_STRAPPING_PINS = {2, 8, 9}
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_c3_validate_gpio_pin(value):
 | 
			
		||||
    if value < 0 or value > 21:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
 | 
			
		||||
    if value in _ESP32C3_SPI_PSRAM_PINS:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
 | 
			
		||||
        )
 | 
			
		||||
    if value in _ESP32C3_STRAPPING_PINS:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "GPIO%d is a Strapping PIN and should be avoided.\n"
 | 
			
		||||
            "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
 | 
			
		||||
            "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_c3_validate_supports(value):
 | 
			
		||||
    num = value[CONF_NUMBER]
 | 
			
		||||
    mode = value[CONF_MODE]
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
 | 
			
		||||
    if num < 0 or num > 21:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
 | 
			
		||||
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
        pass
 | 
			
		||||
    return value
 | 
			
		||||
							
								
								
									
										11
									
								
								esphome/components/esp32/gpio_esp32_h2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								esphome/components/esp32/gpio_esp32_h2.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_h2_validate_gpio_pin(value):
 | 
			
		||||
    # ESP32-H2 not yet supported
 | 
			
		||||
    raise cv.Invalid("ESP32-H2 isn't supported yet")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_h2_validate_supports(value):
 | 
			
		||||
    # ESP32-H2 not yet supported
 | 
			
		||||
    raise cv.Invalid("ESP32-H2 isn't supported yet")
 | 
			
		||||
							
								
								
									
										80
									
								
								esphome/components/esp32/gpio_esp32_s2.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								esphome/components/esp32/gpio_esp32_s2.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
    CONF_OUTPUT,
 | 
			
		||||
    CONF_PULLDOWN,
 | 
			
		||||
    CONF_PULLUP,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
_ESP32S2_SPI_PSRAM_PINS = {
 | 
			
		||||
    26: "SPICS1",
 | 
			
		||||
    27: "SPIHD",
 | 
			
		||||
    28: "SPIWP",
 | 
			
		||||
    29: "SPICS0",
 | 
			
		||||
    30: "SPICLK",
 | 
			
		||||
    31: "SPIQ",
 | 
			
		||||
    32: "SPID",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
_ESP32S2_STRAPPING_PINS = {0, 45, 46}
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_s2_validate_gpio_pin(value):
 | 
			
		||||
    if value < 0 or value > 46:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
 | 
			
		||||
 | 
			
		||||
    if value in _ESP32S2_SPI_PSRAM_PINS:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
 | 
			
		||||
        )
 | 
			
		||||
    if value in _ESP32S2_STRAPPING_PINS:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "GPIO%d is a Strapping PIN and should be avoided.\n"
 | 
			
		||||
            "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
 | 
			
		||||
            "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if value in (22, 23, 24, 25):
 | 
			
		||||
        # These pins are not exposed in GPIO mux (reason unknown)
 | 
			
		||||
        # but they're missing from IO_MUX list in datasheet
 | 
			
		||||
        raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.")
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_s2_validate_supports(value):
 | 
			
		||||
    num = value[CONF_NUMBER]
 | 
			
		||||
    mode = value[CONF_MODE]
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
    is_output = mode[CONF_OUTPUT]
 | 
			
		||||
    is_pullup = mode[CONF_PULLUP]
 | 
			
		||||
    is_pulldown = mode[CONF_PULLDOWN]
 | 
			
		||||
 | 
			
		||||
    if num < 0 or num > 46:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
        pass
 | 
			
		||||
    if is_output and num == 46:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} does not support output pin mode.",
 | 
			
		||||
            [CONF_MODE, CONF_OUTPUT],
 | 
			
		||||
        )
 | 
			
		||||
    if is_pullup and num == 46:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP]
 | 
			
		||||
        )
 | 
			
		||||
    if is_pulldown and num == 46:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
							
								
								
									
										74
									
								
								esphome/components/esp32/gpio_esp32_s3.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/esp32/gpio_esp32_s3.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_INPUT,
 | 
			
		||||
    CONF_MODE,
 | 
			
		||||
    CONF_NUMBER,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
 | 
			
		||||
_ESP_32S3_SPI_PSRAM_PINS = {
 | 
			
		||||
    26: "SPICS1",
 | 
			
		||||
    27: "SPIHD",
 | 
			
		||||
    28: "SPIWP",
 | 
			
		||||
    29: "SPICS0",
 | 
			
		||||
    30: "SPICLK",
 | 
			
		||||
    31: "SPIQ",
 | 
			
		||||
    32: "SPID",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
_ESP_32_ESP32_S3R8_PSRAM_PINS = {
 | 
			
		||||
    33: "SPIIO4",
 | 
			
		||||
    34: "SPIIO5",
 | 
			
		||||
    35: "SPIIO6",
 | 
			
		||||
    36: "SPIIO7",
 | 
			
		||||
    37: "SPIDQS",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46}
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_s3_validate_gpio_pin(value):
 | 
			
		||||
    if value < 0 or value > 48:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
 | 
			
		||||
 | 
			
		||||
    if value in _ESP_32S3_SPI_PSRAM_PINS:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})"
 | 
			
		||||
        )
 | 
			
		||||
    if value in _ESP_32_ESP32_S3R8_PSRAM_PINS:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if value in _ESP_32S3_STRAPPING_PINS:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
            "GPIO%d is a Strapping PIN and should be avoided.\n"
 | 
			
		||||
            "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
 | 
			
		||||
            "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
 | 
			
		||||
            value,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if value in (22, 23, 24, 25):
 | 
			
		||||
        # These pins are not exposed in GPIO mux (reason unknown)
 | 
			
		||||
        # but they're missing from IO_MUX list in datasheet
 | 
			
		||||
        raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.")
 | 
			
		||||
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def esp32_s3_validate_supports(value):
 | 
			
		||||
    num = value[CONF_NUMBER]
 | 
			
		||||
    mode = value[CONF_MODE]
 | 
			
		||||
    is_input = mode[CONF_INPUT]
 | 
			
		||||
 | 
			
		||||
    if num < 0 or num > 48:
 | 
			
		||||
        raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
 | 
			
		||||
    if is_input:
 | 
			
		||||
        # All ESP32 pins support input mode
 | 
			
		||||
        pass
 | 
			
		||||
    return value
 | 
			
		||||
@@ -10,38 +10,7 @@ static const char *const TAG = "esp32";
 | 
			
		||||
 | 
			
		||||
bool IDFInternalGPIOPin::isr_service_installed = false;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  gpio_num_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::setup() {
 | 
			
		||||
  pin_mode(flags_);
 | 
			
		||||
  gpio_set_drive_capability(pin_, drive_strength_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  gpio_config_t conf{};
 | 
			
		||||
  conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
 | 
			
		||||
  conf.mode = flags_to_mode(flags);
 | 
			
		||||
  conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
 | 
			
		||||
  conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
 | 
			
		||||
  conf.intr_type = GPIO_INTR_DISABLE;
 | 
			
		||||
  gpio_config(&conf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
 | 
			
		||||
 | 
			
		||||
gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
  flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
 | 
			
		||||
  if (flags == gpio::FLAG_NONE) {
 | 
			
		||||
    return GPIO_MODE_DISABLE;
 | 
			
		||||
@@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  gpio_num_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
 | 
			
		||||
  auto *arg = new ISRPinArg{};  // NOLINT(cppcoreguidelines-owning-memory)
 | 
			
		||||
  arg->pin = pin_;
 | 
			
		||||
  arg->inverted = inverted_;
 | 
			
		||||
  return ISRInternalGPIOPin((void *) arg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
 | 
			
		||||
  gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
 | 
			
		||||
  switch (type) {
 | 
			
		||||
@@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const {
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::setup() {
 | 
			
		||||
  gpio_config_t conf{};
 | 
			
		||||
  conf.pin_bit_mask = 1ULL << static_cast<uint32_t>(pin_);
 | 
			
		||||
  conf.mode = flags_to_mode(flags_);
 | 
			
		||||
  conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
 | 
			
		||||
  conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
 | 
			
		||||
  conf.intr_type = GPIO_INTR_DISABLE;
 | 
			
		||||
  gpio_config(&conf);
 | 
			
		||||
  gpio_set_drive_capability(pin_, drive_strength_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  // can't call gpio_config here because that logs in esp-idf which may cause issues
 | 
			
		||||
  gpio_set_direction(pin_, flags_to_mode(flags));
 | 
			
		||||
  gpio_pull_mode_t pull_mode = GPIO_FLOATING;
 | 
			
		||||
  if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    pull_mode = GPIO_PULLUP_PULLDOWN;
 | 
			
		||||
  } else if (flags & gpio::FLAG_PULLUP) {
 | 
			
		||||
    pull_mode = GPIO_PULLUP_ONLY;
 | 
			
		||||
  } else if (flags & gpio::FLAG_PULLDOWN) {
 | 
			
		||||
    pull_mode = GPIO_PULLDOWN_ONLY;
 | 
			
		||||
  }
 | 
			
		||||
  gpio_set_pull_mode(pin_, pull_mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
 | 
			
		||||
void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
 | 
			
		||||
void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32
 | 
			
		||||
 | 
			
		||||
using namespace esp32;
 | 
			
		||||
@@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  // not supported
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  gpio_set_direction(arg->pin, flags_to_mode(flags));
 | 
			
		||||
  gpio_pull_mode_t pull_mode = GPIO_FLOATING;
 | 
			
		||||
  if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    pull_mode = GPIO_PULLUP_PULLDOWN;
 | 
			
		||||
  } else if (flags & gpio::FLAG_PULLUP) {
 | 
			
		||||
    pull_mode = GPIO_PULLUP_ONLY;
 | 
			
		||||
  } else if (flags & gpio::FLAG_PULLDOWN) {
 | 
			
		||||
    pull_mode = GPIO_PULLDOWN_ONLY;
 | 
			
		||||
  }
 | 
			
		||||
  gpio_set_pull_mode(arg->pin, pull_mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
 | 
			
		||||
  bool digital_read() override;
 | 
			
		||||
  void digital_write(bool value) override;
 | 
			
		||||
  std::string dump_summary() const override;
 | 
			
		||||
  void detach_interrupt() const override { gpio_intr_disable(pin_); }
 | 
			
		||||
  void detach_interrupt() const override;
 | 
			
		||||
  ISRInternalGPIOPin to_isr() const override;
 | 
			
		||||
  uint8_t get_pin() const override { return (uint8_t) pin_; }
 | 
			
		||||
  bool is_inverted() const override { return inverted_; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  static gpio_mode_t flags_to_mode(gpio::Flags flags);
 | 
			
		||||
  void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
 | 
			
		||||
 | 
			
		||||
  gpio_num_t pin_;
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"]
 | 
			
		||||
 | 
			
		||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
 | 
			
		||||
CONF_SCAN_PARAMETERS = "scan_parameters"
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,8 @@ import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_DISABLED_BY_DEFAULT,
 | 
			
		||||
    CONF_FREQUENCY,
 | 
			
		||||
    CONF_ID,
 | 
			
		||||
    CONF_NAME,
 | 
			
		||||
    CONF_PIN,
 | 
			
		||||
    CONF_SCL,
 | 
			
		||||
    CONF_SDA,
 | 
			
		||||
@@ -17,8 +15,9 @@ from esphome.const import (
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import CORE
 | 
			
		||||
from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
			
		||||
from esphome.cpp_helpers import setup_entity
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES = ["esp32", "api"]
 | 
			
		||||
DEPENDENCIES = ["esp32"]
 | 
			
		||||
 | 
			
		||||
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
 | 
			
		||||
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
 | 
			
		||||
@@ -63,11 +62,9 @@ CONF_TEST_PATTERN = "test_pattern"
 | 
			
		||||
 | 
			
		||||
camera_range_param = cv.int_range(min=-2, max=2)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ESP32Camera),
 | 
			
		||||
        cv.Required(CONF_NAME): cv.string,
 | 
			
		||||
        cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean,
 | 
			
		||||
        cv.Required(CONF_DATA_PINS): cv.All(
 | 
			
		||||
            [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8)
 | 
			
		||||
        ),
 | 
			
		||||
@@ -127,8 +124,8 @@ SETTERS = {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
 | 
			
		||||
    cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await setup_entity(var, config)
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
    for key, setter in SETTERS.items():
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ void ESP32Camera::dump_config() {
 | 
			
		||||
  auto conf = this->config_;
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ESP32 Camera:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Name: %s", this->name_.c_str());
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Internal: %s", YESNO(this->internal_));
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Board Has PSRAM: %s", YESNO(psramFound()));
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
@@ -185,6 +186,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
 | 
			
		||||
 | 
			
		||||
  global_esp32_camera = this;
 | 
			
		||||
}
 | 
			
		||||
ESP32Camera::ESP32Camera() : ESP32Camera("") {}
 | 
			
		||||
void ESP32Camera::set_data_pins(std::array<uint8_t, 8> pins) {
 | 
			
		||||
  this->config_.pin_d0 = pins[0];
 | 
			
		||||
  this->config_.pin_d1 = pins[1];
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ enum ESP32CameraFrameSize {
 | 
			
		||||
class ESP32Camera : public Component, public EntityBase {
 | 
			
		||||
 public:
 | 
			
		||||
  ESP32Camera(const std::string &name);
 | 
			
		||||
  ESP32Camera();
 | 
			
		||||
  void set_data_pins(std::array<uint8_t, 8> pins);
 | 
			
		||||
  void set_vsync_pin(uint8_t pin);
 | 
			
		||||
  void set_href_pin(uint8_t pin);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								esphome/components/esp32_camera_web_server/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/esp32_camera_web_server/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
from esphome.const import CONF_ID, CONF_PORT, CONF_MODE
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@ayufan"]
 | 
			
		||||
DEPENDENCIES = ["esp32_camera"]
 | 
			
		||||
MULTI_CONF = True
 | 
			
		||||
 | 
			
		||||
esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server")
 | 
			
		||||
CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component)
 | 
			
		||||
Mode = esp32_camera_web_server_ns.enum("Mode")
 | 
			
		||||
 | 
			
		||||
MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT}
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(CameraWebServer),
 | 
			
		||||
        cv.Required(CONF_PORT): cv.port,
 | 
			
		||||
        cv.Required(CONF_MODE): cv.enum(MODES, upper=True),
 | 
			
		||||
    },
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    server = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    cg.add(server.set_port(config[CONF_PORT]))
 | 
			
		||||
    cg.add(server.set_mode(config[CONF_MODE]))
 | 
			
		||||
    await cg.register_component(server, config)
 | 
			
		||||
							
								
								
									
										240
									
								
								esphome/components/esp32_camera_web_server/camera_web_server.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								esphome/components/esp32_camera_web_server/camera_web_server.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "camera_web_server.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/util.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <esp_http_server.h>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_camera_web_server {
 | 
			
		||||
 | 
			
		||||
static const int IMAGE_REQUEST_TIMEOUT = 2000;
 | 
			
		||||
static const char *const TAG = "esp32_camera_web_server";
 | 
			
		||||
 | 
			
		||||
#define PART_BOUNDARY "123456789000000000000987654321"
 | 
			
		||||
#define CONTENT_TYPE "image/jpeg"
 | 
			
		||||
#define CONTENT_LENGTH "Content-Length"
 | 
			
		||||
 | 
			
		||||
static const char *const STREAM_HEADER =
 | 
			
		||||
    "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
 | 
			
		||||
    "\r\n";
 | 
			
		||||
static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
 | 
			
		||||
static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
 | 
			
		||||
static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
 | 
			
		||||
 | 
			
		||||
CameraWebServer::CameraWebServer() {}
 | 
			
		||||
 | 
			
		||||
CameraWebServer::~CameraWebServer() {}
 | 
			
		||||
 | 
			
		||||
void CameraWebServer::setup() {
 | 
			
		||||
  if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) {
 | 
			
		||||
    this->mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->semaphore_ = xSemaphoreCreateBinary();
 | 
			
		||||
 | 
			
		||||
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
 | 
			
		||||
  config.server_port = this->port_;
 | 
			
		||||
  config.ctrl_port = this->port_;
 | 
			
		||||
  config.max_open_sockets = 1;
 | 
			
		||||
  config.backlog_conn = 2;
 | 
			
		||||
 | 
			
		||||
  if (httpd_start(&this->httpd_, &config) != ESP_OK) {
 | 
			
		||||
    mark_failed();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpd_uri_t uri = {
 | 
			
		||||
      .uri = "/",
 | 
			
		||||
      .method = HTTP_GET,
 | 
			
		||||
      .handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); },
 | 
			
		||||
      .user_ctx = this};
 | 
			
		||||
 | 
			
		||||
  httpd_register_uri_handler(this->httpd_, &uri);
 | 
			
		||||
 | 
			
		||||
  esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
 | 
			
		||||
    if (this->running_) {
 | 
			
		||||
      this->image_ = std::move(image);
 | 
			
		||||
      xSemaphoreGive(this->semaphore_);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CameraWebServer::on_shutdown() {
 | 
			
		||||
  this->running_ = false;
 | 
			
		||||
  this->image_ = nullptr;
 | 
			
		||||
  httpd_stop(this->httpd_);
 | 
			
		||||
  this->httpd_ = nullptr;
 | 
			
		||||
  vSemaphoreDelete(this->semaphore_);
 | 
			
		||||
  this->semaphore_ = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CameraWebServer::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Port: %d", this->port_);
 | 
			
		||||
  if (this->mode_ == STREAM)
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Mode: stream");
 | 
			
		||||
  else
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  Mode: snapshot");
 | 
			
		||||
 | 
			
		||||
  if (this->is_failed()) {
 | 
			
		||||
    ESP_LOGE(TAG, "  Setup Failed");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; }
 | 
			
		||||
 | 
			
		||||
void CameraWebServer::loop() {
 | 
			
		||||
  if (!this->running_) {
 | 
			
		||||
    this->image_ = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::shared_ptr<esphome::esp32_camera::CameraImage> CameraWebServer::wait_for_image_() {
 | 
			
		||||
  std::shared_ptr<esphome::esp32_camera::CameraImage> image;
 | 
			
		||||
  image.swap(this->image_);
 | 
			
		||||
 | 
			
		||||
  if (!image) {
 | 
			
		||||
    // retry as we might still be fetching image
 | 
			
		||||
    xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS);
 | 
			
		||||
    image.swap(this->image_);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return image;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t CameraWebServer::handler_(struct httpd_req *req) {
 | 
			
		||||
  esp_err_t res = ESP_FAIL;
 | 
			
		||||
 | 
			
		||||
  this->image_ = nullptr;
 | 
			
		||||
  this->running_ = true;
 | 
			
		||||
 | 
			
		||||
  switch (this->mode_) {
 | 
			
		||||
    case STREAM:
 | 
			
		||||
      res = this->streaming_handler_(req);
 | 
			
		||||
      break;
 | 
			
		||||
 | 
			
		||||
    case SNAPSHOT:
 | 
			
		||||
      res = this->snapshot_handler_(req);
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->running_ = false;
 | 
			
		||||
  this->image_ = nullptr;
 | 
			
		||||
  return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) {
 | 
			
		||||
  int ret;
 | 
			
		||||
 | 
			
		||||
  while (buf_len > 0) {
 | 
			
		||||
    ret = httpd_send(r, buf, buf_len);
 | 
			
		||||
    if (ret < 0) {
 | 
			
		||||
      return ESP_FAIL;
 | 
			
		||||
    }
 | 
			
		||||
    buf += ret;
 | 
			
		||||
    buf_len -= ret;
 | 
			
		||||
  }
 | 
			
		||||
  return ESP_OK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
 | 
			
		||||
  esp_err_t res = ESP_OK;
 | 
			
		||||
  char part_buf[64];
 | 
			
		||||
 | 
			
		||||
  // This manually constructs HTTP response to avoid chunked encoding
 | 
			
		||||
  // which is not supported by some clients
 | 
			
		||||
 | 
			
		||||
  res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER));
 | 
			
		||||
  if (res != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "STREAM: failed to set HTTP header");
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint32_t last_frame = millis();
 | 
			
		||||
  uint32_t frames = 0;
 | 
			
		||||
 | 
			
		||||
  while (res == ESP_OK && this->running_) {
 | 
			
		||||
    if (esp32_camera::global_esp32_camera != nullptr) {
 | 
			
		||||
      esp32_camera::global_esp32_camera->request_stream();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto image = this->wait_for_image_();
 | 
			
		||||
 | 
			
		||||
    if (!image) {
 | 
			
		||||
      ESP_LOGW(TAG, "STREAM: failed to acquire frame");
 | 
			
		||||
      res = ESP_FAIL;
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
 | 
			
		||||
      res = httpd_send_all(req, part_buf, hlen);
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
 | 
			
		||||
    }
 | 
			
		||||
    if (res == ESP_OK) {
 | 
			
		||||
      frames++;
 | 
			
		||||
      int64_t frame_time = millis() - last_frame;
 | 
			
		||||
      last_frame = millis();
 | 
			
		||||
 | 
			
		||||
      ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time,
 | 
			
		||||
               1000.0 / (uint32_t) frame_time);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!frames) {
 | 
			
		||||
    res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
 | 
			
		||||
 | 
			
		||||
  return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
 | 
			
		||||
  esp_err_t res = ESP_OK;
 | 
			
		||||
 | 
			
		||||
  if (esp32_camera::global_esp32_camera != nullptr) {
 | 
			
		||||
    esp32_camera::global_esp32_camera->request_image();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  auto image = this->wait_for_image_();
 | 
			
		||||
 | 
			
		||||
  if (!image) {
 | 
			
		||||
    ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame");
 | 
			
		||||
    httpd_resp_send_500(req);
 | 
			
		||||
    res = ESP_FAIL;
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  res = httpd_resp_set_type(req, CONTENT_TYPE);
 | 
			
		||||
  if (res != ESP_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type");
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
 | 
			
		||||
 | 
			
		||||
  if (res == ESP_OK) {
 | 
			
		||||
    res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
 | 
			
		||||
  }
 | 
			
		||||
  if (res == ESP_OK) {
 | 
			
		||||
    res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
 | 
			
		||||
  }
 | 
			
		||||
  return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_camera_web_server
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include <freertos/FreeRTOS.h>
 | 
			
		||||
#include <freertos/semphr.h>
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/esp32_camera/esp32_camera.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/preferences.h"
 | 
			
		||||
 | 
			
		||||
struct httpd_req;
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace esp32_camera_web_server {
 | 
			
		||||
 | 
			
		||||
enum Mode { STREAM, SNAPSHOT };
 | 
			
		||||
 | 
			
		||||
class CameraWebServer : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  CameraWebServer();
 | 
			
		||||
  ~CameraWebServer();
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void on_shutdown() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
  float get_setup_priority() const override;
 | 
			
		||||
  void set_port(uint16_t port) { this->port_ = port; }
 | 
			
		||||
  void set_mode(Mode mode) { this->mode_ = mode; }
 | 
			
		||||
  void loop() override;
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  std::shared_ptr<esphome::esp32_camera::CameraImage> wait_for_image_();
 | 
			
		||||
  esp_err_t handler_(struct httpd_req *req);
 | 
			
		||||
  esp_err_t streaming_handler_(struct httpd_req *req);
 | 
			
		||||
  esp_err_t snapshot_handler_(struct httpd_req *req);
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  uint16_t port_{0};
 | 
			
		||||
  void *httpd_{nullptr};
 | 
			
		||||
  SemaphoreHandle_t semaphore_;
 | 
			
		||||
  std::shared_ptr<esphome::esp32_camera::CameraImage> image_;
 | 
			
		||||
  bool running_{false};
 | 
			
		||||
  Mode mode_{STREAM};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace esp32_camera_web_server
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_ESP32
 | 
			
		||||
@@ -11,6 +11,7 @@ namespace esphome {
 | 
			
		||||
namespace esp32_improv {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp32_improv.component";
 | 
			
		||||
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
 | 
			
		||||
 | 
			
		||||
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
 | 
			
		||||
 | 
			
		||||
@@ -124,8 +125,13 @@ void ESP32ImprovComponent::loop() {
 | 
			
		||||
        this->cancel_timeout("wifi-connect-timeout");
 | 
			
		||||
        this->set_state_(improv::STATE_PROVISIONED);
 | 
			
		||||
 | 
			
		||||
        std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
 | 
			
		||||
        std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, {url});
 | 
			
		||||
        std::vector<std::string> urls = {ESPHOME_MY_LINK};
 | 
			
		||||
#ifdef USE_WEBSERVER
 | 
			
		||||
        auto ip = wifi::global_wifi_component->wifi_sta_ip();
 | 
			
		||||
        std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
 | 
			
		||||
        urls.push_back(webserver_url);
 | 
			
		||||
#endif
 | 
			
		||||
        std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
 | 
			
		||||
        this->send_response_(data);
 | 
			
		||||
        this->set_timeout("end-service", 1000, [this] {
 | 
			
		||||
          this->service_->stop();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
 | 
			
		||||
#include "esp32_touch.h"
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +94,6 @@ void ESP32TouchComponent::dump_config() {
 | 
			
		||||
 | 
			
		||||
  if (this->iir_filter_enabled_()) {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "    IIR Filter: %ums", this->iir_filter_);
 | 
			
		||||
    touch_pad_filter_start(this->iir_filter_);
 | 
			
		||||
  } else {
 | 
			
		||||
    ESP_LOGCONFIG(TAG, "  IIR Filter DISABLED");
 | 
			
		||||
  }
 | 
			
		||||
@@ -125,6 +125,8 @@ void ESP32TouchComponent::loop() {
 | 
			
		||||
    if (should_print) {
 | 
			
		||||
      ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    App.feed_wdt();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (should_print) {
 | 
			
		||||
 
 | 
			
		||||
@@ -93,12 +93,12 @@ def _arduino_check_versions(value):
 | 
			
		||||
    platform_version = value.get(CONF_PLATFORM_VERSION)
 | 
			
		||||
    if platform_version is None:
 | 
			
		||||
        if version >= cv.Version(3, 0, 0):
 | 
			
		||||
            platform_version = ARDUINO_3_PLATFORM_VERSION
 | 
			
		||||
            platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
 | 
			
		||||
        elif version >= cv.Version(2, 5, 0):
 | 
			
		||||
            platform_version = ARDUINO_2_PLATFORM_VERSION
 | 
			
		||||
            platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))
 | 
			
		||||
        else:
 | 
			
		||||
            platform_version = cv.Version(1, 8, 0)
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = str(platform_version)
 | 
			
		||||
            platform_version = _parse_platform_version(str(cv.Version(1, 8, 0)))
 | 
			
		||||
    value[CONF_PLATFORM_VERSION] = platform_version
 | 
			
		||||
 | 
			
		||||
    if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
 | 
			
		||||
        _LOGGER.warning(
 | 
			
		||||
@@ -109,13 +109,22 @@ def _arduino_check_versions(value):
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _parse_platform_version(value):
 | 
			
		||||
    try:
 | 
			
		||||
        # if platform version is a valid version constraint, prefix the default package
 | 
			
		||||
        cv.platformio_version_constraint(value)
 | 
			
		||||
        return f"platformio/espressif8266 @ {value}"
 | 
			
		||||
    except cv.Invalid:
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF_PLATFORM_VERSION = "platform_version"
 | 
			
		||||
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
 | 
			
		||||
    cv.Schema(
 | 
			
		||||
        {
 | 
			
		||||
            cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_SOURCE): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
 | 
			
		||||
            cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
 | 
			
		||||
        }
 | 
			
		||||
    ),
 | 
			
		||||
    _arduino_check_versions,
 | 
			
		||||
@@ -142,21 +151,22 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    cg.add(esp8266_ns.setup_preferences())
 | 
			
		||||
 | 
			
		||||
    cg.add_platformio_option("lib_ldf_mode", "off")
 | 
			
		||||
 | 
			
		||||
    cg.add_platformio_option("board", config[CONF_BOARD])
 | 
			
		||||
    cg.add_build_flag("-DUSE_ESP8266")
 | 
			
		||||
    cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
 | 
			
		||||
    cg.add_define("ESPHOME_VARIANT", "ESP8266")
 | 
			
		||||
 | 
			
		||||
    conf = config[CONF_FRAMEWORK]
 | 
			
		||||
    cg.add_platformio_option("framework", "arduino")
 | 
			
		||||
    cg.add_build_flag("-DUSE_ARDUINO")
 | 
			
		||||
    cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
 | 
			
		||||
    cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
 | 
			
		||||
    cg.add_platformio_option(
 | 
			
		||||
        "platform_packages",
 | 
			
		||||
        [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
 | 
			
		||||
    )
 | 
			
		||||
    cg.add_platformio_option(
 | 
			
		||||
        "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Default for platformio is LWIP2_LOW_MEMORY with:
 | 
			
		||||
    #  - MSS=536
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ void IRAM_ATTR HOT yield() { ::yield(); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
 | 
			
		||||
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
 | 
			
		||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
 | 
			
		||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
 | 
			
		||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
 | 
			
		||||
void arch_restart() {
 | 
			
		||||
  ESP.restart();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
  // restart() doesn't always end execution
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,29 @@ namespace esp8266 {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "esp8266";
 | 
			
		||||
 | 
			
		||||
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    return INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    return OUTPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    if (pin == 16) {
 | 
			
		||||
      // GPIO16 doesn't have a pullup, so pinMode would fail.
 | 
			
		||||
      // However, sometimes this method is called with pullup mode anyway
 | 
			
		||||
      // for example from dallas one_wire. For those cases convert this
 | 
			
		||||
      // to a INPUT mode.
 | 
			
		||||
      return INPUT;
 | 
			
		||||
    }
 | 
			
		||||
    return INPUT_PULLUP;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    return INPUT_PULLDOWN_16;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
    return OUTPUT_OPEN_DRAIN;
 | 
			
		||||
  } else {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ISRPinArg {
 | 
			
		||||
  uint8_t pin;
 | 
			
		||||
  bool inverted;
 | 
			
		||||
@@ -43,28 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int
 | 
			
		||||
  attachInterruptArg(pin_, func, arg, arduino_mode);
 | 
			
		||||
}
 | 
			
		||||
void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  uint8_t mode;
 | 
			
		||||
  if (flags == gpio::FLAG_INPUT) {
 | 
			
		||||
    mode = INPUT;
 | 
			
		||||
  } else if (flags == gpio::FLAG_OUTPUT) {
 | 
			
		||||
    mode = OUTPUT;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
 | 
			
		||||
    mode = INPUT_PULLUP;
 | 
			
		||||
    if (pin_ == 16) {
 | 
			
		||||
      // GPIO16 doesn't have a pullup, so pinMode would fail.
 | 
			
		||||
      // However, sometimes this method is called with pullup mode anyway
 | 
			
		||||
      // for example from dallas one_wire. For those cases convert this
 | 
			
		||||
      // to a INPUT mode.
 | 
			
		||||
      mode = INPUT;
 | 
			
		||||
    }
 | 
			
		||||
  } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
 | 
			
		||||
    mode = INPUT_PULLDOWN_16;
 | 
			
		||||
  } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
 | 
			
		||||
    mode = OUTPUT_OPEN_DRAIN;
 | 
			
		||||
  } else {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  pinMode(pin_, mode);  // NOLINT
 | 
			
		||||
  pinMode(pin_, flags_to_mode(flags, pin_));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string ESP8266GPIOPin::dump_summary() const {
 | 
			
		||||
@@ -97,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
 | 
			
		||||
}
 | 
			
		||||
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
 | 
			
		||||
  auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
 | 
			
		||||
  pinMode(arg->pin, flags_to_mode(flags, arg->pin));  // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() {
 | 
			
		||||
 | 
			
		||||
  esp_err_t err;
 | 
			
		||||
  err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
 | 
			
		||||
  ESPHL_ERROR_CHECK(err, "ETH set hostname error");
 | 
			
		||||
  if (err != ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  tcpip_adapter_ip_info_t info;
 | 
			
		||||
  if (this->manual_ip_.has_value()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -43,19 +43,27 @@ def validate_source_shorthand(value):
 | 
			
		||||
    # Regex for GitHub repo name with optional branch/tag
 | 
			
		||||
    # Note: git allows other branch/tag names as well, but never seen them used before
 | 
			
		||||
    m = re.match(
 | 
			
		||||
        r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?",
 | 
			
		||||
        r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))",
 | 
			
		||||
        value,
 | 
			
		||||
    )
 | 
			
		||||
    if m is None:
 | 
			
		||||
        raise cv.Invalid(
 | 
			
		||||
            "Source is not a file system path or in expected github://username/name[@branch-or-tag] format!"
 | 
			
		||||
            "Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!"
 | 
			
		||||
        )
 | 
			
		||||
    conf = {
 | 
			
		||||
        CONF_TYPE: TYPE_GIT,
 | 
			
		||||
        CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
 | 
			
		||||
    }
 | 
			
		||||
    if m.group(3):
 | 
			
		||||
        conf[CONF_REF] = m.group(3)
 | 
			
		||||
    if m.group(4):
 | 
			
		||||
        conf = {
 | 
			
		||||
            CONF_TYPE: TYPE_GIT,
 | 
			
		||||
            CONF_URL: "https://github.com/esphome/esphome.git",
 | 
			
		||||
            CONF_REF: f"pull/{m.group(4)}/head",
 | 
			
		||||
        }
 | 
			
		||||
    else:
 | 
			
		||||
        conf = {
 | 
			
		||||
            CONF_TYPE: TYPE_GIT,
 | 
			
		||||
            CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
 | 
			
		||||
        }
 | 
			
		||||
        if m.group(3):
 | 
			
		||||
            conf[CONF_REF] = m.group(3)
 | 
			
		||||
 | 
			
		||||
    return SOURCE_SCHEMA(conf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ void EZOSensor::loop() {
 | 
			
		||||
  if (buf[0] != 1)
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  float val = strtof((char *) &buf[1], nullptr);
 | 
			
		||||
  float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 1).value_or(0);
 | 
			
		||||
  this->publish_state(val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ from esphome.const import (
 | 
			
		||||
    CONF_LAST_FINGER_ID,
 | 
			
		||||
    CONF_SECURITY_LEVEL,
 | 
			
		||||
    CONF_STATUS,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    ICON_ACCOUNT,
 | 
			
		||||
    ICON_ACCOUNT_CHECK,
 | 
			
		||||
    ICON_DATABASE,
 | 
			
		||||
@@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
            icon=ICON_FINGERPRINT,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_NONE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_STATUS): sensor.sensor_schema(
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_NONE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_CAPACITY): sensor.sensor_schema(
 | 
			
		||||
            icon=ICON_DATABASE,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_NONE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema(
 | 
			
		||||
            icon=ICON_SECURITY,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_NONE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema(
 | 
			
		||||
            icon=ICON_ACCOUNT,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_NONE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema(
 | 
			
		||||
            icon=ICON_ACCOUNT_CHECK,
 | 
			
		||||
            accuracy_decimals=0,
 | 
			
		||||
            state_class=STATE_CLASS_NONE,
 | 
			
		||||
            entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor";
 | 
			
		||||
void HomeassistantSensor::setup() {
 | 
			
		||||
  api::global_api_server->subscribe_home_assistant_state(
 | 
			
		||||
      this->entity_id_, this->attribute_, [this](const std::string &state) {
 | 
			
		||||
        auto val = parse_float(state);
 | 
			
		||||
        auto val = parse_number<float>(state);
 | 
			
		||||
        if (!val.has_value()) {
 | 
			
		||||
          ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
 | 
			
		||||
          this->publish_state(NAN);
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() {
 | 
			
		||||
 | 
			
		||||
    if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' &&
 | 
			
		||||
        this->buffer_.back() == static_cast<char>(ASCII_CR)) {
 | 
			
		||||
      int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10);
 | 
			
		||||
      int millimeters = parse_number<int>(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0);
 | 
			
		||||
      float meters = float(millimeters) / 1000.0;
 | 
			
		||||
      ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters);
 | 
			
		||||
      this->publish_state(meters);
 | 
			
		||||
 
 | 
			
		||||
@@ -96,6 +96,8 @@ async def to_code(config):
 | 
			
		||||
    if CORE.is_esp32:
 | 
			
		||||
        cg.add_library("WiFiClientSecure", None)
 | 
			
		||||
        cg.add_library("HTTPClient", None)
 | 
			
		||||
    if CORE.is_esp8266:
 | 
			
		||||
        cg.add_library("ESP8266HTTPClient", None)
 | 
			
		||||
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@ static const char *const TAG = "htu21d";
 | 
			
		||||
 | 
			
		||||
static const uint8_t HTU21D_ADDRESS = 0x40;
 | 
			
		||||
static const uint8_t HTU21D_REGISTER_RESET = 0xFE;
 | 
			
		||||
static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3;
 | 
			
		||||
static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5;
 | 
			
		||||
static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3;
 | 
			
		||||
static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5;
 | 
			
		||||
static const uint8_t HTU21D_REGISTER_STATUS = 0xE7;
 | 
			
		||||
 | 
			
		||||
void HTU21DComponent::setup() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "i2c_bus.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
#include "esphome/core/optional.h"
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <vector>
 | 
			
		||||
@@ -32,16 +33,8 @@ class I2CRegister {
 | 
			
		||||
 | 
			
		||||
// like ntohs/htons but without including networking headers.
 | 
			
		||||
// ("i2c" byte order is big-endian)
 | 
			
		||||
inline uint16_t i2ctohs(uint16_t i2cshort) {
 | 
			
		||||
  union {
 | 
			
		||||
    uint16_t x;
 | 
			
		||||
    uint8_t y[2];
 | 
			
		||||
  } conv;
 | 
			
		||||
  conv.x = i2cshort;
 | 
			
		||||
  return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); }
 | 
			
		||||
inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); }
 | 
			
		||||
inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); }
 | 
			
		||||
 | 
			
		||||
class I2CDevice {
 | 
			
		||||
 public:
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,32 @@
 | 
			
		||||
 | 
			
		||||
namespace improv {
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data) {
 | 
			
		||||
  return parse_improv_data(data.data(), data.size());
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum) {
 | 
			
		||||
  return parse_improv_data(data.data(), data.size(), check_checksum);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
 | 
			
		||||
  ImprovCommand improv_command;
 | 
			
		||||
  Command command = (Command) data[0];
 | 
			
		||||
  uint8_t data_length = data[1];
 | 
			
		||||
 | 
			
		||||
  if (data_length != length - 3) {
 | 
			
		||||
    return {.command = UNKNOWN};
 | 
			
		||||
  if (data_length != length - 2 - check_checksum) {
 | 
			
		||||
    improv_command.command = UNKNOWN;
 | 
			
		||||
    return improv_command;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = data[length - 1];
 | 
			
		||||
  if (check_checksum) {
 | 
			
		||||
    uint8_t checksum = data[length - 1];
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
  for (uint8_t i = 0; i < length - 1; i++) {
 | 
			
		||||
    calculated_checksum += data[i];
 | 
			
		||||
  }
 | 
			
		||||
    uint32_t calculated_checksum = 0;
 | 
			
		||||
    for (uint8_t i = 0; i < length - 1; i++) {
 | 
			
		||||
      calculated_checksum += data[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  if ((uint8_t) calculated_checksum != checksum) {
 | 
			
		||||
    return {.command = BAD_CHECKSUM};
 | 
			
		||||
    if ((uint8_t) calculated_checksum != checksum) {
 | 
			
		||||
      improv_command.command = BAD_CHECKSUM;
 | 
			
		||||
      return improv_command;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (command == WIFI_SETTINGS) {
 | 
			
		||||
@@ -39,12 +44,11 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
 | 
			
		||||
    return {.command = command, .ssid = ssid, .password = password};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
      .command = command,
 | 
			
		||||
  };
 | 
			
		||||
  improv_command.command = command;
 | 
			
		||||
  return improv_command;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum) {
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, bool add_checksum) {
 | 
			
		||||
  std::vector<uint8_t> out;
 | 
			
		||||
  uint32_t length = 0;
 | 
			
		||||
  out.push_back(command);
 | 
			
		||||
@@ -56,17 +60,19 @@ std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::
 | 
			
		||||
  }
 | 
			
		||||
  out.insert(out.begin() + 1, length);
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
  if (add_checksum) {
 | 
			
		||||
    uint32_t calculated_checksum = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t byte : out) {
 | 
			
		||||
    calculated_checksum += byte;
 | 
			
		||||
    for (uint8_t byte : out) {
 | 
			
		||||
      calculated_checksum += byte;
 | 
			
		||||
    }
 | 
			
		||||
    out.push_back(calculated_checksum);
 | 
			
		||||
  }
 | 
			
		||||
  out.push_back(calculated_checksum);
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum) {
 | 
			
		||||
#ifdef ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum) {
 | 
			
		||||
  std::vector<uint8_t> out;
 | 
			
		||||
  uint32_t length = 0;
 | 
			
		||||
  out.push_back(command);
 | 
			
		||||
@@ -78,14 +84,16 @@ std::vector<uint8_t> build_rpc_response(Command command, const std::vector<Strin
 | 
			
		||||
  }
 | 
			
		||||
  out.insert(out.begin() + 1, length);
 | 
			
		||||
 | 
			
		||||
  uint32_t calculated_checksum = 0;
 | 
			
		||||
  if (add_checksum) {
 | 
			
		||||
    uint32_t calculated_checksum = 0;
 | 
			
		||||
 | 
			
		||||
  for (uint8_t byte : out) {
 | 
			
		||||
    calculated_checksum += byte;
 | 
			
		||||
    for (uint8_t byte : out) {
 | 
			
		||||
      calculated_checksum += byte;
 | 
			
		||||
    }
 | 
			
		||||
    out.push_back(calculated_checksum);
 | 
			
		||||
  }
 | 
			
		||||
  out.push_back(calculated_checksum);
 | 
			
		||||
  return out;
 | 
			
		||||
}
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
#endif  // ARDUINO
 | 
			
		||||
 | 
			
		||||
}  // namespace improv
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#ifdef ARDUINO
 | 
			
		||||
#include "WString.h"
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
#endif  // ARDUINO
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include <string>
 | 
			
		||||
@@ -38,6 +38,8 @@ enum Command : uint8_t {
 | 
			
		||||
  UNKNOWN = 0x00,
 | 
			
		||||
  WIFI_SETTINGS = 0x01,
 | 
			
		||||
  IDENTIFY = 0x02,
 | 
			
		||||
  GET_CURRENT_STATE = 0x02,
 | 
			
		||||
  GET_DEVICE_INFO = 0x03,
 | 
			
		||||
  BAD_CHECKSUM = 0xFF,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -49,12 +51,13 @@ struct ImprovCommand {
 | 
			
		||||
  std::string password;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data);
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
 | 
			
		||||
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum = true);
 | 
			
		||||
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true);
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum);
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum);
 | 
			
		||||
#endif  // USE_ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum,
 | 
			
		||||
                                        bool add_checksum = true);
 | 
			
		||||
#ifdef ARDUINO
 | 
			
		||||
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum = true);
 | 
			
		||||
#endif  // ARDUINO
 | 
			
		||||
 | 
			
		||||
}  // namespace improv
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								esphome/components/improv_serial/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/improv_serial/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
import esphome.final_validate as fv
 | 
			
		||||
 | 
			
		||||
CODEOWNERS = ["@esphome/core"]
 | 
			
		||||
DEPENDENCIES = ["logger", "wifi"]
 | 
			
		||||
AUTO_LOAD = ["improv"]
 | 
			
		||||
 | 
			
		||||
improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
 | 
			
		||||
 | 
			
		||||
ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
    {
 | 
			
		||||
        cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
 | 
			
		||||
    }
 | 
			
		||||
).extend(cv.COMPONENT_SCHEMA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_logger_baud_rate(config):
 | 
			
		||||
    logger_conf = fv.full_config.get()[CONF_LOGGER]
 | 
			
		||||
    if logger_conf[CONF_BAUD_RATE] == 0:
 | 
			
		||||
        raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def to_code(config):
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_ID])
 | 
			
		||||
    await cg.register_component(var, config)
 | 
			
		||||
							
								
								
									
										266
									
								
								esphome/components/improv_serial/improv_serial_component.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								esphome/components/improv_serial/improv_serial_component.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,266 @@
 | 
			
		||||
#include "improv_serial_component.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/application.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/hal.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/core/version.h"
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/logger/logger.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace improv_serial {
 | 
			
		||||
 | 
			
		||||
static const char *const TAG = "improv_serial";
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::setup() {
 | 
			
		||||
  global_improv_serial_component = this;
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  this->hw_serial_ = logger::global_logger->get_hw_serial();
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  this->uart_num_ = logger::global_logger->get_uart_num();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  if (wifi::global_wifi_component->has_sta()) {
 | 
			
		||||
    this->state_ = improv::STATE_PROVISIONED;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
 | 
			
		||||
 | 
			
		||||
int ImprovSerialComponent::available_() {
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  return this->hw_serial_->available();
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  size_t available;
 | 
			
		||||
  uart_get_buffered_data_len(this->uart_num_, &available);
 | 
			
		||||
  return available;
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t ImprovSerialComponent::read_byte_() {
 | 
			
		||||
  uint8_t data;
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  this->hw_serial_->readBytes(&data, 1);
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
 | 
			
		||||
#endif
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
 | 
			
		||||
  data.push_back('\n');
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  this->hw_serial_->write(data.data(), data.size());
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  uart_write_bytes(this->uart_num_, data.data(), data.size());
 | 
			
		||||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::loop() {
 | 
			
		||||
  const uint32_t now = millis();
 | 
			
		||||
  if (now - this->last_read_byte_ > 50) {
 | 
			
		||||
    this->rx_buffer_.clear();
 | 
			
		||||
    this->last_read_byte_ = now;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  while (this->available_()) {
 | 
			
		||||
    uint8_t byte = this->read_byte_();
 | 
			
		||||
    if (this->parse_improv_serial_byte_(byte)) {
 | 
			
		||||
      this->last_read_byte_ = now;
 | 
			
		||||
    } else {
 | 
			
		||||
      this->rx_buffer_.clear();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (this->state_ == improv::STATE_PROVISIONING) {
 | 
			
		||||
    if (wifi::global_wifi_component->is_connected()) {
 | 
			
		||||
      wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
 | 
			
		||||
                                                 this->connecting_sta_.get_password());
 | 
			
		||||
      this->connecting_sta_ = {};
 | 
			
		||||
      this->cancel_timeout("wifi-connect-timeout");
 | 
			
		||||
      this->set_state_(improv::STATE_PROVISIONED);
 | 
			
		||||
 | 
			
		||||
      std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
 | 
			
		||||
      this->send_response_(url);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
 | 
			
		||||
  std::vector<std::string> urls;
 | 
			
		||||
#ifdef USE_WEBSERVER
 | 
			
		||||
  auto ip = wifi::global_wifi_component->wifi_sta_ip();
 | 
			
		||||
  std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
 | 
			
		||||
  urls.push_back(webserver_url);
 | 
			
		||||
#endif
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
 | 
			
		||||
  return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<uint8_t> ImprovSerialComponent::build_version_info_() {
 | 
			
		||||
  std::vector<std::string> infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
 | 
			
		||||
  std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
 | 
			
		||||
  return data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) {
 | 
			
		||||
  size_t at = this->rx_buffer_.size();
 | 
			
		||||
  this->rx_buffer_.push_back(byte);
 | 
			
		||||
  ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte);
 | 
			
		||||
  const uint8_t *raw = &this->rx_buffer_[0];
 | 
			
		||||
  if (at == 0)
 | 
			
		||||
    return byte == 'I';
 | 
			
		||||
  if (at == 1)
 | 
			
		||||
    return byte == 'M';
 | 
			
		||||
  if (at == 2)
 | 
			
		||||
    return byte == 'P';
 | 
			
		||||
  if (at == 3)
 | 
			
		||||
    return byte == 'R';
 | 
			
		||||
  if (at == 4)
 | 
			
		||||
    return byte == 'O';
 | 
			
		||||
  if (at == 5)
 | 
			
		||||
    return byte == 'V';
 | 
			
		||||
 | 
			
		||||
  if (at == 6)
 | 
			
		||||
    return byte == IMPROV_SERIAL_VERSION;
 | 
			
		||||
 | 
			
		||||
  if (at == 7)
 | 
			
		||||
    return true;
 | 
			
		||||
  uint8_t type = raw[7];
 | 
			
		||||
 | 
			
		||||
  if (at == 8)
 | 
			
		||||
    return true;
 | 
			
		||||
  uint8_t data_len = raw[8];
 | 
			
		||||
 | 
			
		||||
  if (at < 8 + data_len)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  if (at == 8 + data_len)
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
  if (at == 8 + data_len + 1) {
 | 
			
		||||
    uint8_t checksum = 0x00;
 | 
			
		||||
    for (uint8_t i = 0; i < at; i++)
 | 
			
		||||
      checksum += raw[i];
 | 
			
		||||
 | 
			
		||||
    if (checksum != byte) {
 | 
			
		||||
      ESP_LOGW(TAG, "Error decoding Improv payload");
 | 
			
		||||
      this->set_error_(improv::ERROR_INVALID_RPC);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (type == TYPE_RPC) {
 | 
			
		||||
      this->set_error_(improv::ERROR_NONE);
 | 
			
		||||
      auto command = improv::parse_improv_data(&raw[9], data_len, false);
 | 
			
		||||
      return this->parse_improv_payload_(command);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // If we got here then the command coming is is improv, but not an RPC command
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
 | 
			
		||||
  switch (command.command) {
 | 
			
		||||
    case improv::WIFI_SETTINGS: {
 | 
			
		||||
      wifi::WiFiAP sta{};
 | 
			
		||||
      sta.set_ssid(command.ssid);
 | 
			
		||||
      sta.set_password(command.password);
 | 
			
		||||
      this->connecting_sta_ = sta;
 | 
			
		||||
 | 
			
		||||
      wifi::global_wifi_component->set_sta(sta);
 | 
			
		||||
      wifi::global_wifi_component->start_scanning();
 | 
			
		||||
      this->set_state_(improv::STATE_PROVISIONING);
 | 
			
		||||
      ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
 | 
			
		||||
               command.password.c_str());
 | 
			
		||||
 | 
			
		||||
      auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
 | 
			
		||||
      this->set_timeout("wifi-connect-timeout", 30000, f);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case improv::GET_CURRENT_STATE:
 | 
			
		||||
      this->set_state_(this->state_);
 | 
			
		||||
      if (this->state_ == improv::STATE_PROVISIONED) {
 | 
			
		||||
        std::vector<uint8_t> url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
 | 
			
		||||
        this->send_response_(url);
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
    case improv::GET_DEVICE_INFO: {
 | 
			
		||||
      std::vector<uint8_t> info = this->build_version_info_();
 | 
			
		||||
      this->send_response_(info);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    default: {
 | 
			
		||||
      ESP_LOGW(TAG, "Unknown Improv payload");
 | 
			
		||||
      this->set_error_(improv::ERROR_UNKNOWN_RPC);
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::set_state_(improv::State state) {
 | 
			
		||||
  this->state_ = state;
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
 | 
			
		||||
  data.resize(11);
 | 
			
		||||
  data[6] = IMPROV_SERIAL_VERSION;
 | 
			
		||||
  data[7] = TYPE_CURRENT_STATE;
 | 
			
		||||
  data[8] = 1;
 | 
			
		||||
  data[9] = state;
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = 0x00;
 | 
			
		||||
  for (uint8_t d : data)
 | 
			
		||||
    checksum += d;
 | 
			
		||||
  data[10] = checksum;
 | 
			
		||||
 | 
			
		||||
  this->write_data_(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::set_error_(improv::Error error) {
 | 
			
		||||
  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
 | 
			
		||||
  data.resize(11);
 | 
			
		||||
  data[6] = IMPROV_SERIAL_VERSION;
 | 
			
		||||
  data[7] = TYPE_ERROR_STATE;
 | 
			
		||||
  data[8] = 1;
 | 
			
		||||
  data[9] = error;
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = 0x00;
 | 
			
		||||
  for (uint8_t d : data)
 | 
			
		||||
    checksum += d;
 | 
			
		||||
  data[10] = checksum;
 | 
			
		||||
  this->write_data_(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::send_response_(std::vector<uint8_t> &response) {
 | 
			
		||||
  std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
 | 
			
		||||
  data.resize(9);
 | 
			
		||||
  data[6] = IMPROV_SERIAL_VERSION;
 | 
			
		||||
  data[7] = TYPE_RPC_RESPONSE;
 | 
			
		||||
  data[8] = response.size();
 | 
			
		||||
  data.insert(data.end(), response.begin(), response.end());
 | 
			
		||||
 | 
			
		||||
  uint8_t checksum = 0x00;
 | 
			
		||||
  for (uint8_t d : data)
 | 
			
		||||
    checksum += d;
 | 
			
		||||
  data.push_back(checksum);
 | 
			
		||||
 | 
			
		||||
  this->write_data_(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ImprovSerialComponent::on_wifi_connect_timeout_() {
 | 
			
		||||
  this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
 | 
			
		||||
  this->set_state_(improv::STATE_AUTHORIZED);
 | 
			
		||||
  ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
 | 
			
		||||
  wifi::global_wifi_component->clear_sta();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ImprovSerialComponent *global_improv_serial_component =  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
    nullptr;                                             // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace improv_serial
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										69
									
								
								esphome/components/improv_serial/improv_serial_component.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphome/components/improv_serial/improv_serial_component.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/components/improv/improv.h"
 | 
			
		||||
#include "esphome/components/wifi/wifi_component.h"
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
#include <HardwareSerial.h>
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
#include <driver/uart.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace improv_serial {
 | 
			
		||||
 | 
			
		||||
enum ImprovSerialType : uint8_t {
 | 
			
		||||
  TYPE_CURRENT_STATE = 0x01,
 | 
			
		||||
  TYPE_ERROR_STATE = 0x02,
 | 
			
		||||
  TYPE_RPC = 0x03,
 | 
			
		||||
  TYPE_RPC_RESPONSE = 0x04
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const uint8_t IMPROV_SERIAL_VERSION = 1;
 | 
			
		||||
 | 
			
		||||
class ImprovSerialComponent : public Component {
 | 
			
		||||
 public:
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void loop() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::HARDWARE; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  bool parse_improv_serial_byte_(uint8_t byte);
 | 
			
		||||
  bool parse_improv_payload_(improv::ImprovCommand &command);
 | 
			
		||||
 | 
			
		||||
  void set_state_(improv::State state);
 | 
			
		||||
  void set_error_(improv::Error error);
 | 
			
		||||
  void send_response_(std::vector<uint8_t> &response);
 | 
			
		||||
  void on_wifi_connect_timeout_();
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> build_rpc_settings_response_(improv::Command command);
 | 
			
		||||
  std::vector<uint8_t> build_version_info_();
 | 
			
		||||
 | 
			
		||||
  int available_();
 | 
			
		||||
  uint8_t read_byte_();
 | 
			
		||||
  void write_data_(std::vector<uint8_t> &data);
 | 
			
		||||
 | 
			
		||||
#ifdef USE_ARDUINO
 | 
			
		||||
  HardwareSerial *hw_serial_{nullptr};
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef USE_ESP_IDF
 | 
			
		||||
  uart_port_t uart_num_;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  std::vector<uint8_t> rx_buffer_;
 | 
			
		||||
  uint32_t last_read_byte_{0};
 | 
			
		||||
  wifi::WiFiAP connecting_sta_;
 | 
			
		||||
  improv::State state_{improv::STATE_AUTHORIZED};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern ImprovSerialComponent
 | 
			
		||||
    *global_improv_serial_component;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
 | 
			
		||||
 | 
			
		||||
}  // namespace improv_serial
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
@@ -9,6 +9,7 @@ from esphome.const import (
 | 
			
		||||
    DEVICE_CLASS_BATTERY,
 | 
			
		||||
    DEVICE_CLASS_HUMIDITY,
 | 
			
		||||
    DEVICE_CLASS_TEMPERATURE,
 | 
			
		||||
    ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
    STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    UNIT_CELSIUS,
 | 
			
		||||
    UNIT_PERCENT,
 | 
			
		||||
@@ -53,6 +54,7 @@ CONFIG_SCHEMA = (
 | 
			
		||||
                accuracy_decimals=0,
 | 
			
		||||
                device_class=DEVICE_CLASS_BATTERY,
 | 
			
		||||
                state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
                entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
 | 
			
		||||
            ),
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,7 @@ class AddressableLight : public LightOutput, public Component {
 | 
			
		||||
  void mark_shown_() {
 | 
			
		||||
#ifdef USE_POWER_SUPPLY
 | 
			
		||||
    for (const auto &c : *this) {
 | 
			
		||||
      if (c.get().is_on()) {
 | 
			
		||||
      if (c.get_red_raw() > 0 || c.get_green_raw() > 0 || c.get_blue_raw() > 0 || c.get_white_raw() > 0) {
 | 
			
		||||
        this->power_.request();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -221,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
 | 
			
		||||
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
 | 
			
		||||
  this->log_callback_.add(std::move(callback));
 | 
			
		||||
}
 | 
			
		||||
float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
 | 
			
		||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
 | 
			
		||||
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
 | 
			
		||||
#ifdef USE_ESP32
 | 
			
		||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay"
 | 
			
		||||
CONF_SCROLL_ENABLE = "scroll_enable"
 | 
			
		||||
CONF_SCROLL_MODE = "scroll_mode"
 | 
			
		||||
CONF_REVERSE_ENABLE = "reverse_enable"
 | 
			
		||||
CONF_NUM_CHIP_LINES = "num_chip_lines"
 | 
			
		||||
CONF_CHIP_LINES_STYLE = "chip_lines_style"
 | 
			
		||||
 | 
			
		||||
integration_ns = cg.esphome_ns.namespace("max7219digit")
 | 
			
		||||
ChipLinesStyle = integration_ns.enum("ChipLinesStyle")
 | 
			
		||||
CHIP_LINES_STYLE = {
 | 
			
		||||
    "ZIGZAG": ChipLinesStyle.ZIGZAG,
 | 
			
		||||
    "SNAKE": ChipLinesStyle.SNAKE,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ScrollMode = integration_ns.enum("ScrollMode")
 | 
			
		||||
SCROLL_MODES = {
 | 
			
		||||
    "CONTINUOUS": 0,
 | 
			
		||||
    "STOP": 1,
 | 
			
		||||
    "CONTINUOUS": ScrollMode.CONTINUOUS,
 | 
			
		||||
    "STOP": ScrollMode.STOP,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CHIP_MODES = {
 | 
			
		||||
@@ -37,6 +47,10 @@ CONFIG_SCHEMA = (
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(): cv.declare_id(MAX7219Component),
 | 
			
		||||
            cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255),
 | 
			
		||||
            cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255),
 | 
			
		||||
            cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum(
 | 
			
		||||
                CHIP_LINES_STYLE, upper=True
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15),
 | 
			
		||||
            cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True),
 | 
			
		||||
            cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum(
 | 
			
		||||
@@ -67,6 +81,8 @@ async def to_code(config):
 | 
			
		||||
    await display.register_display(var, config)
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
 | 
			
		||||
    cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES]))
 | 
			
		||||
    cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE]))
 | 
			
		||||
    cg.add(var.set_intensity(config[CONF_INTENSITY]))
 | 
			
		||||
    cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP]))
 | 
			
		||||
    cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED]))
 | 
			
		||||
 
 | 
			
		||||
@@ -26,9 +26,12 @@ void MAX7219Component::setup() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS...");
 | 
			
		||||
  this->spi_setup();
 | 
			
		||||
  this->stepsleft_ = 0;
 | 
			
		||||
  this->max_displaybuffer_.reserve(500);  // Create base space to write buffer
 | 
			
		||||
  // Initialize buffer with 0 for display so all non written pixels are blank
 | 
			
		||||
  this->max_displaybuffer_.resize(this->num_chips_ * 8, 0);
 | 
			
		||||
  for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
 | 
			
		||||
    std::vector<uint8_t> vec(1);
 | 
			
		||||
    this->max_displaybuffer_.push_back(vec);
 | 
			
		||||
    // Initialize buffer with 0 for display so all non written pixels are blank
 | 
			
		||||
    this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0);
 | 
			
		||||
  }
 | 
			
		||||
  // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
 | 
			
		||||
  this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7);
 | 
			
		||||
  // let's use our own ASCII -> led pattern encoding
 | 
			
		||||
@@ -46,6 +49,8 @@ void MAX7219Component::setup() {
 | 
			
		||||
void MAX7219Component::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "MAX7219DIGIT:");
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Number of Chips: %u", this->num_chips_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Number of Chips Lines: %u", this->num_chip_lines_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Chips Lines Style : %u", this->chip_lines_style_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Intensity: %u", this->intensity_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Scroll Mode: %u", this->scroll_mode_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Scroll Speed: %u", this->scroll_speed_);
 | 
			
		||||
@@ -59,19 +64,19 @@ void MAX7219Component::loop() {
 | 
			
		||||
  uint32_t now = millis();
 | 
			
		||||
 | 
			
		||||
  // check if the buffer has shrunk past the current position since last update
 | 
			
		||||
  if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) ||
 | 
			
		||||
      (this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) {
 | 
			
		||||
  if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) ||
 | 
			
		||||
      (this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) {
 | 
			
		||||
    this->stepsleft_ = 0;
 | 
			
		||||
    this->display();
 | 
			
		||||
    this->old_buffer_size_ = this->max_displaybuffer_.size();
 | 
			
		||||
    this->old_buffer_size_ = this->max_displaybuffer_[0].size();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Reset the counter back to 0 when full string has been displayed.
 | 
			
		||||
  if (this->stepsleft_ > this->max_displaybuffer_.size())
 | 
			
		||||
  if (this->stepsleft_ > this->max_displaybuffer_[0].size())
 | 
			
		||||
    this->stepsleft_ = 0;
 | 
			
		||||
 | 
			
		||||
  // Return if there is no need to scroll or scroll is off
 | 
			
		||||
  if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) {
 | 
			
		||||
  if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) {
 | 
			
		||||
    this->display();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
@@ -82,8 +87,8 @@ void MAX7219Component::loop() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Dwell time at end of string in case of stop at end
 | 
			
		||||
  if (this->scroll_mode_ == 1) {
 | 
			
		||||
    if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) {
 | 
			
		||||
  if (this->scroll_mode_ == ScrollMode::STOP) {
 | 
			
		||||
    if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) {
 | 
			
		||||
      if (now - this->last_scroll_ >= this->scroll_dwell_) {
 | 
			
		||||
        this->stepsleft_ = 0;
 | 
			
		||||
        this->last_scroll_ = now;
 | 
			
		||||
@@ -107,30 +112,53 @@ void MAX7219Component::display() {
 | 
			
		||||
  // Run this routine for the rows of every chip 8x row 0 top to 7 bottom
 | 
			
		||||
  // Fill the pixel parameter with display data
 | 
			
		||||
  // Send the data to the chip
 | 
			
		||||
  for (uint8_t i = 0; i < this->num_chips_; i++) {
 | 
			
		||||
    for (uint8_t j = 0; j < 8; j++) {
 | 
			
		||||
      if (this->reverse_) {
 | 
			
		||||
        pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
 | 
			
		||||
      } else {
 | 
			
		||||
        pixels[j] = this->max_displaybuffer_[i * 8 + j];
 | 
			
		||||
  for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) {
 | 
			
		||||
    for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
 | 
			
		||||
      for (uint8_t j = 0; j < 8; j++) {
 | 
			
		||||
        bool reverse =
 | 
			
		||||
            chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_;
 | 
			
		||||
        if (reverse) {
 | 
			
		||||
          pixels[j] =
 | 
			
		||||
              this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j];
 | 
			
		||||
        } else {
 | 
			
		||||
          pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
 | 
			
		||||
        this->orientation_ = orientation_180_();
 | 
			
		||||
      this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels);
 | 
			
		||||
      if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
 | 
			
		||||
        this->orientation_ = orientation_180_();
 | 
			
		||||
    }
 | 
			
		||||
    this->send64pixels(i, pixels);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t MAX7219Component::orientation_180_() {
 | 
			
		||||
  switch (this->orientation_) {
 | 
			
		||||
    case 0:
 | 
			
		||||
      return 2;
 | 
			
		||||
    case 1:
 | 
			
		||||
      return 3;
 | 
			
		||||
    case 2:
 | 
			
		||||
      return 0;
 | 
			
		||||
    case 3:
 | 
			
		||||
      return 1;
 | 
			
		||||
    default:
 | 
			
		||||
      return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MAX7219Component::get_height_internal() {
 | 
			
		||||
  return 8;  // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER
 | 
			
		||||
             // TO BE DONE -> CREATE Virtual size of screen and scroll
 | 
			
		||||
  return 8 * this->num_chip_lines_;  // TO BE DONE -> CREATE Virtual size of screen and scroll
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; }
 | 
			
		||||
 | 
			
		||||
size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; }
 | 
			
		||||
int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; }
 | 
			
		||||
 | 
			
		||||
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
 | 
			
		||||
  if (x + 1 > this->max_displaybuffer_.size()) {  // Extend the display buffer in case required
 | 
			
		||||
    this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
 | 
			
		||||
  if (x + 1 > this->max_displaybuffer_[0].size()) {  // Extend the display buffer in case required
 | 
			
		||||
    for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
 | 
			
		||||
      this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((y >= this->get_height_internal()) || (y < 0) || (x < 0))  // If pixel is outside display then dont draw
 | 
			
		||||
@@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
 | 
			
		||||
  uint8_t subpos = y;  // Y is starting at 0 top left
 | 
			
		||||
 | 
			
		||||
  if (color.is_on()) {
 | 
			
		||||
    this->max_displaybuffer_[pos] |= (1 << subpos);
 | 
			
		||||
    this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8);
 | 
			
		||||
  } else {
 | 
			
		||||
    this->max_displaybuffer_[pos] &= ~(1 << subpos);
 | 
			
		||||
    this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
 | 
			
		||||
}
 | 
			
		||||
void MAX7219Component::update() {
 | 
			
		||||
  this->update_ = true;
 | 
			
		||||
  this->max_displaybuffer_.clear();
 | 
			
		||||
  this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_);
 | 
			
		||||
  for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
 | 
			
		||||
    this->max_displaybuffer_[chip_line].clear();
 | 
			
		||||
    this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_);
 | 
			
		||||
  }
 | 
			
		||||
  if (this->writer_local_.has_value())  // insert Labda function if available
 | 
			
		||||
    (*this->writer_local_)(*this);
 | 
			
		||||
}
 | 
			
		||||
@@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
 | 
			
		||||
void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
 | 
			
		||||
  this->set_scroll(on_off);
 | 
			
		||||
  this->set_scroll_mode(mode);
 | 
			
		||||
  this->set_scroll_speed(speed);
 | 
			
		||||
@@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_
 | 
			
		||||
  this->set_scroll_delay(delay);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX7219Component::scroll(bool on_off, uint8_t mode) {
 | 
			
		||||
void MAX7219Component::scroll(bool on_off, ScrollMode mode) {
 | 
			
		||||
  this->set_scroll(on_off);
 | 
			
		||||
  this->set_scroll_mode(mode);
 | 
			
		||||
}
 | 
			
		||||
@@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) {
 | 
			
		||||
void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); }
 | 
			
		||||
 | 
			
		||||
void MAX7219Component::scroll_left() {
 | 
			
		||||
  if (this->update_) {
 | 
			
		||||
    this->max_displaybuffer_.push_back(this->bckgrnd_);
 | 
			
		||||
    for (uint16_t i = 0; i < this->stepsleft_; i++) {
 | 
			
		||||
      this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
 | 
			
		||||
      this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
 | 
			
		||||
      this->update_ = false;
 | 
			
		||||
  for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
 | 
			
		||||
    if (this->update_) {
 | 
			
		||||
      this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_);
 | 
			
		||||
      for (uint16_t i = 0; i < this->stepsleft_; i++) {
 | 
			
		||||
        this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
 | 
			
		||||
        this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
 | 
			
		||||
      this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
 | 
			
		||||
    this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
 | 
			
		||||
  }
 | 
			
		||||
  this->update_ = false;
 | 
			
		||||
  this->stepsleft_++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MAX7219Component::send_char(uint8_t chip, uint8_t data) {
 | 
			
		||||
  // get this character from PROGMEM
 | 
			
		||||
  for (uint8_t i = 0; i < 8; i++)
 | 
			
		||||
    this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
 | 
			
		||||
    this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
 | 
			
		||||
}  // end of send_char
 | 
			
		||||
 | 
			
		||||
// send one character (data) to position (chip)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,16 @@
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace max7219digit {
 | 
			
		||||
 | 
			
		||||
enum ChipLinesStyle {
 | 
			
		||||
  ZIGZAG = 0,
 | 
			
		||||
  SNAKE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum ScrollMode {
 | 
			
		||||
  CONTINUOUS = 0,
 | 
			
		||||
  STOP,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MAX7219Component;
 | 
			
		||||
 | 
			
		||||
using max7219_writer_t = std::function<void(MAX7219Component &)>;
 | 
			
		||||
@@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent,
 | 
			
		||||
 | 
			
		||||
  void set_intensity(uint8_t intensity) { this->intensity_ = intensity; };
 | 
			
		||||
  void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; };
 | 
			
		||||
  void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; };
 | 
			
		||||
  void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; };
 | 
			
		||||
  void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; };
 | 
			
		||||
  void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; };
 | 
			
		||||
  void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; };
 | 
			
		||||
  void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
 | 
			
		||||
  void set_scroll(bool on_off) { this->scroll_ = on_off; };
 | 
			
		||||
  void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; };
 | 
			
		||||
  void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; };
 | 
			
		||||
  void set_reverse(bool on_off) { this->reverse_ = on_off; };
 | 
			
		||||
 | 
			
		||||
  void send_char(uint8_t chip, uint8_t data);
 | 
			
		||||
  void send64pixels(uint8_t chip, const uint8_t pixels[8]);
 | 
			
		||||
 | 
			
		||||
  void scroll_left();
 | 
			
		||||
  void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell);
 | 
			
		||||
  void scroll(bool on_off, uint8_t mode);
 | 
			
		||||
  void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell);
 | 
			
		||||
  void scroll(bool on_off, ScrollMode mode);
 | 
			
		||||
  void scroll(bool on_off);
 | 
			
		||||
  void intensity(uint8_t intensity);
 | 
			
		||||
 | 
			
		||||
@@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent,
 | 
			
		||||
 protected:
 | 
			
		||||
  void send_byte_(uint8_t a_register, uint8_t data);
 | 
			
		||||
  void send_to_all_(uint8_t a_register, uint8_t data);
 | 
			
		||||
  uint8_t orientation_180_();
 | 
			
		||||
 | 
			
		||||
  uint8_t intensity_;  /// Intensity of the display from 0 to 15 (most)
 | 
			
		||||
  uint8_t num_chips_;
 | 
			
		||||
  uint8_t num_chip_lines_;
 | 
			
		||||
  ChipLinesStyle chip_lines_style_;
 | 
			
		||||
  bool scroll_;
 | 
			
		||||
  bool reverse_;
 | 
			
		||||
  bool update_{false};
 | 
			
		||||
@@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent,
 | 
			
		||||
  uint16_t scroll_delay_;
 | 
			
		||||
  uint16_t scroll_dwell_;
 | 
			
		||||
  uint16_t old_buffer_size_ = 0;
 | 
			
		||||
  uint8_t scroll_mode_;
 | 
			
		||||
  ScrollMode scroll_mode_;
 | 
			
		||||
  bool invert_ = false;
 | 
			
		||||
  uint8_t orientation_;
 | 
			
		||||
  uint8_t bckgrnd_ = 0x0;
 | 
			
		||||
  std::vector<uint8_t> max_displaybuffer_;
 | 
			
		||||
  std::vector<std::vector<uint8_t>> max_displaybuffer_;
 | 
			
		||||
  uint32_t last_scroll_ = 0;
 | 
			
		||||
  uint16_t stepsleft_;
 | 
			
		||||
  size_t get_buffer_length_();
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user